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

feat: create ion tour #1118

Merged
merged 35 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
f804361
feat: creating tour
vinicius-guedes-brisa Jul 26, 2024
4433cbe
style: removing console logs
VinicciusSantos Jul 26, 2024
4d2a39f
feat: add story to tour step
VinicciusSantos Jul 26, 2024
7b9e630
style: lint
vinicius-guedes-brisa Jul 26, 2024
ca405d2
feat: adjusting step position
vinicius-guedes-brisa Jul 29, 2024
7e0a71f
fix: fixing infinity loop of invalid range error
vinicius-guedes-brisa Jul 30, 2024
3bcb00b
feat: transition to finish tour
vinicius-guedes-brisa Jul 30, 2024
77ba90f
test: creating unit test to tour-popover
vinicius-guedes-brisa Jul 30, 2024
f0d88eb
test: creating unit test to tour-backdrop
vinicius-guedes-brisa Jul 30, 2024
3f402b8
test: creating unit test to tour-service
vinicius-guedes-brisa Jul 30, 2024
a69cf54
refactor: position calculator callback
vinicius-guedes-brisa Jul 30, 2024
2d8c446
refactor: access modifiers
vinicius-guedes-brisa Jul 30, 2024
60e7dd0
test: creating unit test to tour-directive
vinicius-guedes-brisa Jul 31, 2024
ecc5b07
refactor: removing access modifiers from inputs
vinicius-guedes-brisa Jul 31, 2024
e904d0d
refactor: removing unnecessary subject
vinicius-guedes-brisa Jul 31, 2024
6fddde1
refactor: removing code climate suggestions
vinicius-guedes-brisa Jul 31, 2024
a3a3ed5
refactor: undoing unnecessary changes
vinicius-guedes-brisa Jul 31, 2024
ef8baf7
test: fix: dependency injection order
vinicius-guedes-brisa Jul 31, 2024
c529644
Merge branch 'main' into tour
vinicius-guedes-brisa Jul 31, 2024
b05965e
refactor: adding return type on function
vinicius-guedes-brisa Jul 31, 2024
bcdec4f
docs: adding tour docs
vinicius-guedes-brisa Jul 31, 2024
eb75efa
refactor: using ion popover in steps
vinicius-guedes-brisa Aug 1, 2024
3bf9b8a
style: empty line
vinicius-guedes-brisa Aug 1, 2024
c836184
fix: adjusting popover position
vinicius-guedes-brisa Aug 1, 2024
2d23476
fix: adjusting spacings
vinicius-guedes-brisa Aug 1, 2024
9883e05
test: adjusting tour tests
vinicius-guedes-brisa Aug 1, 2024
153d20c
docs: updating tour props
vinicius-guedes-brisa Aug 1, 2024
497f11d
feat: emmiting events
vinicius-guedes-brisa Aug 2, 2024
6185e49
refactor: simplify action labels
vinicius-guedes-brisa Aug 2, 2024
742eef8
refactor: adding other ionButton props in tour popover actions
vinicius-guedes-brisa Aug 2, 2024
f41d4b4
Update projects/ion/src/lib/tour/tour-backdrop/tour-backdrop.componen…
vinicius-guedes-brisa Aug 5, 2024
aff76fc
docs: updating button control in tourstep
vinicius-guedes-brisa Aug 5, 2024
0ac0c73
test: increase coverage
vinicius-guedes-brisa Aug 5, 2024
8bef7a5
Merge branch 'main' into tour
vinicius-guedes-brisa Aug 5, 2024
2fa4d17
Merge branch 'main' into tour
vinicius-guedes-brisa Aug 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions projects/ion/src/lib/core/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ export * from './tab-group';
export * from './table';
export * from './tag';
export * from './tooltip';
export * from './tour';
export * from './triple-toggle';
27 changes: 27 additions & 0 deletions projects/ion/src/lib/core/types/tour.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { EventEmitter } from '@angular/core';

import { PopoverButtonsProps, PopoverPosition, PopoverProps } from './popover';

export interface IonTourStepProps {
ionStepId: string;
ionTourId: string;
ionStepTitle?: PopoverProps['ionPopoverTitle'];
ionStepBody?: PopoverProps['ionPopoverBody'];
ionPrevStepId?: IonTourStepProps['ionStepId'];
ionNextStepId?: IonTourStepProps['ionStepId'];
ionPrevStepBtn?: PopoverButtonsProps;
ionNextStepBtn?: PopoverButtonsProps;
ionStepPosition?: PopoverPosition;
ionStepMarginToContent?: number;
ionStepBackdropPadding?: number;
ionStepCustomClass?: string;
ionStepBackdropCustomClass?: string;
ionOnPrevStep?: EventEmitter<void>;
ionOnNextStep?: EventEmitter<void>;
ionOnFinishTour?: EventEmitter<void>;
target?: DOMRect;
}

export interface IonStartTourProps {
tourId?: string;
}
4 changes: 4 additions & 0 deletions projects/ion/src/lib/position/position.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export class IonPositionService {
private currentPosition: IonPositions;
private pointAtCenter = true;

public setElementPadding(padding: number): void {
this.elementPadding = padding;
}

public setHostPosition(position: DOMRect): void {
this.hostPosition = position;
}
Expand Down
3 changes: 3 additions & 0 deletions projects/ion/src/lib/tour/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './tour-step.directive';
export * from './tour.module';
export * from './tour.service';
135 changes: 135 additions & 0 deletions projects/ion/src/lib/tour/mocks/tour-basic-demo.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { Component } from '@angular/core';

import { IonTourService } from '../tour.service';
import { IonTourStepProps, PopoverPosition } from '../../core/types';

export enum DemoSteps {
UPLOAD = 'upload',
SAVE = 'save',
MORE_OPTIONS = 'more_options',
}

export const STEP1_MOCK: IonTourStepProps = {
ionStepTitle: 'Upload Action',
ionTourId: 'basic-demo',
ionStepId: DemoSteps.UPLOAD,
ionNextStepId: DemoSteps.SAVE,
ionStepPosition: PopoverPosition.BOTTOM_CENTER,
ionPrevStepBtn: { label: 'Close' },
};

export const STEP2_MOCK: IonTourStepProps = {
ionStepTitle: 'Save Action',
ionTourId: 'basic-demo',
ionStepId: DemoSteps.SAVE,
ionPrevStepId: DemoSteps.UPLOAD,
ionNextStepId: DemoSteps.MORE_OPTIONS,
ionStepPosition: PopoverPosition.BOTTOM_CENTER,
};

export const STEP3_MOCK: IonTourStepProps = {
ionStepTitle: 'More Options',
ionTourId: 'basic-demo',
ionStepId: DemoSteps.MORE_OPTIONS,
ionPrevStepId: DemoSteps.SAVE,
ionStepPosition: PopoverPosition.RIGHT_CENTER,
ionNextStepBtn: { label: 'Finish' },
};

@Component({
template: `
<style>
div {
height: 100%;
margin-top: 200px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 100px;

main {
display: flex;
gap: 10px;
}
}

span {
color: #8d93a5;
font-size: 14px;
}
</style>

<div>
<ion-button label="Begin tour" (ionOnClick)="startTour()"> </ion-button>

<main>
<ion-button
label="Upload"
type="secondary"
ionTourStep
[ionStepTitle]="step1.ionStepTitle"
[ionStepPosition]="step1.ionStepPosition"
[ionTourId]="step1.ionTourId"
[ionStepId]="step1.ionStepId"
[ionNextStepId]="step1.ionNextStepId"
[ionPrevStepBtn]="step1.ionPrevStepBtn"
[ionStepBody]="uploadStepContent"
></ion-button>
<ion-button
label="Save"
ionTourStep
[ionTourId]="step2.ionTourId"
[ionStepId]="step2.ionStepId"
[ionPrevStepId]="step2.ionPrevStepId"
[ionNextStepId]="step2.ionNextStepId"
[ionStepTitle]="step2.ionStepTitle"
[ionStepBody]="saveStep"
></ion-button>
<ion-button
iconType="option"
type="secondary"
ionTourStep
[ionStepPosition]="step3.ionStepPosition"
[ionTourId]="step3.ionTourId"
[ionStepId]="step3.ionStepId"
[ionPrevStepId]="step3.ionPrevStepId"
[ionStepTitle]="step3.ionStepTitle"
[ionStepBody]="optionsStepContent"
[ionNextStepBtn]="step3.ionNextStepBtn"
></ion-button>
</main>
</div>

<ng-template #uploadStepContent>
<span>Here is a random image:</span>
<img
src="https://picsum.photos/200/100"
alt="Random Image"
width="200px"
height="100px"
/>
</ng-template>

<ng-template #saveStep>
<span>Save your changes.</span>
</ng-template>

<ng-template #optionsStepContent>
<span>Click to see other actions.</span>
</ng-template>
`,
})
export class TourBasicDemoComponent {
public tourId = 'basic-demo';

public step1 = STEP1_MOCK;
public step2 = STEP2_MOCK;
public step3 = STEP3_MOCK;

constructor(private readonly ionTourService: IonTourService) {}

public startTour(): void {
this.ionTourService.start();
}
}
74 changes: 74 additions & 0 deletions projects/ion/src/lib/tour/mocks/tour-step-props.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { AfterViewInit, Component, Input, OnChanges } from '@angular/core';

import { PopoverButtonsProps, PopoverPosition } from '../../core/types';
import { IonTourService } from '../tour.service';

@Component({
selector: 'tour-step-props',
template: `
<style>
div {
height: 800px;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
</style>

<div>
<ion-button
label="Demo"
type="secondary"
(ionOnClick)="restartTour()"
ionTourStep
[ionStepId]="ionStepId"
[ionTourId]="ionTourId"
[ionStepBody]="stepBody"
[ionStepTitle]="ionStepTitle"
[ionPrevStepBtn]="ionPrevStepBtn"
[ionNextStepBtn]="ionNextStepBtn"
[ionPrevStepId]="ionPrevStepId"
[ionNextStepId]="ionNextStepId"
[ionStepPosition]="ionStepPosition"
[ionStepMarginToContent]="ionStepMarginToContent"
[ionStepBackdropPadding]="ionStepBackdropPadding"
[ionStepCustomClass]="ionStepCustomClass"
[ionStepBackdropCustomClass]="ionStepBackdropCustomClass"
></ion-button>
</div>

<ng-template #stepBody>
<p>Step body content</p>
</ng-template>
`,
})
export class TourStepDemoComponent implements AfterViewInit, OnChanges {
@Input() ionStepId = 'demo-step';
@Input() ionTourId = 'demo-tour';
@Input() ionStepTitle: string;
@Input() ionPrevStepBtn: PopoverButtonsProps;
@Input() ionNextStepBtn: PopoverButtonsProps;
@Input() ionPrevStepId: string;
@Input() ionNextStepId: string;
@Input() ionStepPosition: PopoverPosition;
@Input() ionStepMarginToContent: number;
@Input() ionStepBackdropPadding: number;
@Input() ionStepCustomClass: string;
@Input() ionStepBackdropCustomClass: string;

constructor(private readonly ionTourService: IonTourService) {}

public ngAfterViewInit(): void {
this.ionTourService.start();
}

public ngOnChanges(): void {
this.restartTour();
}

public restartTour(): void {
this.ionTourService.finish();
this.ionTourService.start();
}
}
1 change: 1 addition & 0 deletions projects/ion/src/lib/tour/tour-backdrop/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './tour-backdrop.component';
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<svg
*ngIf="(isActive && currentStep) || inTransition"
data-testid="ion-tour-backdrop"
[ngClass]="
'ion-tour-backdrop ' +
(currentStep ? currentStep.ionStepBackdropCustomClass : '')
"
[class.ion-tour-backdrop-transition]="inTransition"
[style.clip-path]="clipPath"
></svg>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@import '../../../styles/index.scss';

.ion-tour-backdrop {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: $black-transparence-45;
transition: all 0.4s ease;
z-index: $zIndexMid;

&-transition {
background-color: #00000000;
clip-path: polygon(
0px 0px,
0px 100%,
0px 100%,
0px 0,
100% 0,
100% 100%,
0px 100%,
0px 100%,
100% 100%,
100% 0px
) !important;
}
}

::ng-deep .ion-tour-popover {
position: absolute !important;
transform: translate(0) !important;

.ion-popover__content-body {
max-height: unset !important;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { render, RenderResult, screen } from '@testing-library/angular';

import { IonTourStepProps } from '../../core/types';
import { IonTourBackdropComponent } from './tour-backdrop.component';

const sut = async (
props: Partial<IonTourBackdropComponent> = {}
): Promise<RenderResult<IonTourBackdropComponent>> => {
return render(IonTourBackdropComponent, {
declarations: [IonTourBackdropComponent],
componentProperties: {
...props,
},
});
};

const STEP_MOCK = {
target: {
x: 300,
y: 300,
width: 100,
height: 100,
bottom: 400,
right: 400,
left: 300,
top: 300,
} as DOMRect,
} as IonTourStepProps;

describe('IonTourBackdropComponent', () => {
it('should render', async () => {
await sut();
expect(screen.queryByTestId('ion-tour-backdrop')).toBeInTheDocument();
});

it('should render with custom class', async () => {
const ionStepBackdropCustomClass = 'custom-class';

await sut({
currentStep: { ...STEP_MOCK, ionStepBackdropCustomClass },
});

expect(screen.queryByTestId('ion-tour-backdrop')).toHaveClass(
ionStepBackdropCustomClass
);
});

describe('transitions', () => {
it('should render with transition', async () => {
await sut({ inTransition: true });
expect(screen.queryByTestId('ion-tour-backdrop')).toHaveClass(
'ion-tour-backdrop-transition'
);
});

it('should stop rendering when the transition ends and the tour remains inactive', async () => {
const { rerender } = await sut({ inTransition: true, isActive: false });
rerender({ inTransition: false });
expect(screen.queryByTestId('ion-tour-backdrop')).not.toBeInTheDocument();
});

it('should stop rendering when performFinalTransition is called', async () => {
jest.useFakeTimers();
const { fixture } = await sut({ inTransition: true });
const callback = jest.fn();

fixture.componentInstance.performFinalTransition(callback);

jest.runAllTimers();
expect(screen.queryByTestId('ion-tour-backdrop')).not.toBeInTheDocument();
expect(callback).toHaveBeenCalled();
});
});
});
Loading
Loading