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(stepper): provide component animations #770

Closed
wants to merge 26 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f21b27b
feat(dialog/snackbar): animate component states
SisIvanova Jan 18, 2023
1cdf3e5
feat(toast): animate component states
SisIvanova Jan 18, 2023
740bf83
fix(dialog): failing tests
SisIvanova Jan 18, 2023
d0a87d2
fix(snackbar): hide animation
SisIvanova Jan 18, 2023
f680504
Merge branch 'master' into sivanova/animate-components
simeonoff Jan 26, 2023
95f31cc
Merge branch 'master' into sivanova/animate-components
ChronosSF Jan 31, 2023
6ab661c
Merge branch 'master' into sivanova/animate-components
SisIvanova Apr 19, 2023
9680ea5
Merge branch 'master' into sivanova/animate-components
SisIvanova Apr 26, 2023
d3f152a
Merge branch 'master' into sivanova/animate-components
SisIvanova Apr 27, 2023
33dccf4
fix(dialog): animate backdrop
SisIvanova Apr 28, 2023
26f3255
Update dialog.base.scss
SisIvanova Apr 28, 2023
b8c7484
Merge branch 'master' into sivanova/animate-components
rkaraivanov May 12, 2023
6ce3952
feat(stepper): provide component animations
SisIvanova May 23, 2023
dbe6c57
Merge branch 'master' into sivanova/animate-stepper-component
SisIvanova Jul 17, 2023
9c5f050
Update dialog.ts
SisIvanova Jul 17, 2023
68ce0c6
feat(stepper): implement animations
SisIvanova Aug 1, 2023
a742e2d
Merge branch 'master' into sivanova/animate-stepper-component
SisIvanova Aug 1, 2023
a585001
fix(stepper): lint errors
SisIvanova Aug 1, 2023
8a1c8dc
Update step.ts
SisIvanova Aug 1, 2023
cea81eb
Merge branch 'master' into sivanova/animate-stepper-component
SisIvanova Aug 1, 2023
ada7653
Merge branch 'master' into sivanova/animate-stepper-component
SisIvanova Aug 1, 2023
3ea1eb6
Merge branch 'master' into sivanova/animate-stepper-component
SisIvanova Aug 2, 2023
04ea794
feat(stepper): improve animatios
SisIvanova Aug 8, 2023
b4fc1f5
Merge branch 'master' into sivanova/animate-stepper-component
SisIvanova Aug 8, 2023
899b234
refactor(stepper): animations
SisIvanova Aug 11, 2023
05c7c7e
Merge branch 'master' into sivanova/animate-stepper-component
SisIvanova Aug 11, 2023
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 src/animations/presets/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './grow/index.js';
export * from './fade/index.js';
export * from './slide/index.js';
26 changes: 26 additions & 0 deletions src/animations/presets/slide/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { EaseOut } from '../../easings.js';
import { animation } from '../../types.js';

const slideInLeft = animation(
[
{ opacity: 0, transform: 'translateX(-500px)' },
{ opacity: 1, transform: 'translateX(0)' },
],
{
duration: 350,
easing: EaseOut.Quad,
}
);

const slideInRight = animation(
[
{ opacity: 0, transform: 'translateX(500px)' },
{ opacity: 1, transform: 'translateX(0)' },
],
{
duration: 350,
easing: EaseOut.Quad,
}
);

export { slideInLeft, slideInRight };
112 changes: 110 additions & 2 deletions src/components/stepper/step.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@ import { property, query, queryAssignedElements } from 'lit/decorators.js';
import { when } from 'lit/directives/when.js';
import { watch } from '../common/decorators/watch.js';
import { partNameMap } from '../common/util.js';
import { AnimationPlayer, EaseOut } from '../../animations/index.js';
import { themes } from '../../theming/theming-decorator.js';
import { styles } from './themes/step/light/step.base.css.js';
import { styles as bootstrap } from './themes/step/light/step.bootstrap.css.js';
import { styles as indigo } from './themes/step/light/step.indigo.css.js';
import { styles as fluent } from './themes/step/light/step.fluent.css.js';
import { styles as material } from './themes/step/light/step.material.css.js';
import {
animation,
AnimationReferenceMetadata,
} from '../../animations/types.js';

/**
* The step component is used within the `igc-stepper` element and it holds the content of each step.
Expand Down Expand Up @@ -46,6 +51,9 @@ export default class IgcStepComponent extends LitElement {
public static readonly tagName = 'igc-step';
/** @private */
public static override styles = styles;
private animationPlayer!: AnimationPlayer;
private animationType!: AnimationReferenceMetadata;
public direction = -1;

@queryAssignedElements({ slot: 'title' })
private _titleChildren!: Array<HTMLElement>;
Expand All @@ -61,6 +69,10 @@ export default class IgcStepComponent extends LitElement {
@query('[part~="body"]')
public contentBody!: HTMLElement;

/* blazorSuppress */
@query('[part~="content"]')
public content!: HTMLElement;

/** Gets/sets whether the step is invalid. */
@property({ reflect: true, type: Boolean })
public invalid = false;
Expand Down Expand Up @@ -107,6 +119,18 @@ export default class IgcStepComponent extends LitElement {
@property({ attribute: false })
public orientation: 'horizontal' | 'vertical' = 'horizontal';

/** @private */
@property({ attribute: false })
public animationDuration = 320;

/** @private */
@property({ attribute: false })
public verticalAnimation: 'grow' | 'fade' | 'none' = 'grow';

/** @private */
@property({ attribute: false })
public horizontalAnimation: 'slide' | 'fade' | 'none' = 'slide';

/** @private */
@property({ attribute: false })
public index = -1;
Expand All @@ -123,8 +147,92 @@ export default class IgcStepComponent extends LitElement {
@property({ attribute: false })
public visited = false;

public override firstUpdated() {
this.animationPlayer = new AnimationPlayer(this.content);
}

public async toggleAnimation(dir: 'open' | 'close') {
const position = 100 * this.direction;
// const endPosition = 100 * this.direction;

const slideInLeftFrames: Keyframe[] = [
{ opacity: 1, transform: `translateX(${position}%)` },
{ opacity: 1, transform: 'translateX(0%)' },
];

const slideInRightFrames: Keyframe[] = [
{ opacity: 1, transform: 'translateX(0%)' },
{ opacity: 1, transform: `translateX(${position}%)` },
];

const growVerInFrames: Keyframe[] = [
{ opacity: 0, height: 0 },
{ opacity: 1, height: 'auto' },
];

const growVerOutFrames: Keyframe[] = [
{ opacity: 1, height: 'auto' },
{ opacity: 0, height: 0 },
];

const opts: KeyframeAnimationOptions = {
duration: this.animationDuration,
easing: EaseOut.Quad,
};

const customSlideInLeft = animation(slideInLeftFrames, opts);
const customSlideInRight = animation(slideInRightFrames, opts);
const customSlideAnimation =
dir === 'open' ? customSlideInRight : customSlideInLeft;

const customGrowVerIn = animation(growVerInFrames, opts);
const customGrowVerOut = animation(growVerOutFrames, opts);
const customGrowAnimation =
dir === 'open' ? customGrowVerIn : customGrowVerOut;

const fadeInFrames: Keyframe[] = [{ opacity: 0 }, { opacity: 1 }];

const fadeOpts: KeyframeAnimationOptions = {
duration: this.animationDuration,
easing: EaseOut.Sine,
};

const customFadeAnimation = animation(fadeInFrames, fadeOpts);

if (this.orientation == 'vertical') {
switch (this.verticalAnimation) {
case 'fade':
this.animationType = customFadeAnimation;
break;
case 'none':
return;
default:
this.animationType = customGrowAnimation;
}
}

if (this.orientation == 'horizontal') {
switch (this.horizontalAnimation) {
case 'fade':
this.animationType = customFadeAnimation;
break;
case 'none':
return;
default:
this.animationType = customSlideAnimation;
}
}

const [_, event] = await Promise.all([
this.animationPlayer.stopAll(),
this.animationPlayer.play(this.animationType),
]);

return event.type === 'finish';
}

@watch('active', { waitUntilFirstUpdate: true })
protected activeChange() {
protected async activeChange() {
if (this.active) {
this.dispatchEvent(
new CustomEvent('stepActiveChanged', { bubbles: true, detail: false })
Expand Down Expand Up @@ -237,7 +345,7 @@ export default class IgcStepComponent extends LitElement {
role="tabpanel"
aria-labelledby="igc-step-header-${this.index}"
>
<div part="content">
<div part="content" aria-hidden="${!this.active}">
<slot></slot>
</div>
</div>`;
Expand Down
92 changes: 90 additions & 2 deletions src/components/stepper/stepper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,37 @@ export default class IgcStepperComponent extends EventEmitterMixin<
* @remarks
* The default value is undefined.
* When the stepper is horizontally orientated the title is positioned below the indicator.
* When the stepper is horizontally orientated the title is positioned on the right side of the indicator.
* When the stepper is vertically orientated the title is positioned on the right side of the indicator.
*/
@property({ reflect: false, attribute: 'title-position' })
public titlePosition?: 'bottom' | 'top' | 'end' | 'start';

/**
* Determines the stepper animation duration in ms.
* @remarks
* Default value is `320ms`.
*/
@property({ type: Number, attribute: 'animation-duration' })
public animationDuration = 320;

/**
* Get/Set the vertical animation of the stepper.
*
* @remarks
* Default value is `grow`.
*/
@property({ attribute: false })
public verticalAnimation: 'grow' | 'fade' | 'none' = 'grow';

/**
* Get/Set the horizontal animation of the stepper.
*
* @remarks
* Default value is `slide`.
*/
@property({ attribute: false })
public horizontalAnimation: 'slide' | 'fade' | 'none' = 'slide';

@watch('orientation', { waitUntilFirstUpdate: true })
protected orientationChange(): void {
this.setAttribute('aria-orientation', this.orientation);
Expand All @@ -117,6 +143,30 @@ export default class IgcStepperComponent extends EventEmitterMixin<
);
}

@watch('animationDuration', { waitUntilFirstUpdate: true })
protected animationDurationChange(): void {
this.steps.forEach(
(step: IgcStepComponent) =>
(step.animationDuration = this.animationDuration)
);
}

@watch('verticalAnimation', { waitUntilFirstUpdate: true })
protected verticalAnimationChange(): void {
this.steps.forEach(
(step: IgcStepComponent) =>
(step.verticalAnimation = this.verticalAnimation)
);
}

@watch('horizontalAnimation', { waitUntilFirstUpdate: true })
protected horizontalAnimationChange(): void {
this.steps.forEach(
(step: IgcStepComponent) =>
(step.horizontalAnimation = this.horizontalAnimation)
);
}

@watch('stepType', { waitUntilFirstUpdate: true })
protected stepTypeChange(): void {
this.steps.forEach(
Expand Down Expand Up @@ -206,7 +256,7 @@ export default class IgcStepperComponent extends EventEmitterMixin<
}
}

private activateStep(step: IgcStepComponent, shouldEmit = true) {
private async activateStep(step: IgcStepComponent, shouldEmit = true) {
if (step === this.activeStep) {
return;
}
Expand All @@ -221,6 +271,41 @@ export default class IgcStepperComponent extends EventEmitterMixin<
cancelable: true,
};

if (
this.orientation == 'horizontal' &&
this.horizontalAnimation == 'slide'
) {
if (step.index < this.activeStep.index) {
// this.activeStep.direction = 1;
// this.activeStep.toggleAnimation('open');
step.toggleAnimation('close');
console.log(
'previous step',
`next: ${step.index}`,
`active: ${this.activeStep.index}`
);
// this.activeStep.direction = -1;
} else {
this.activeStep.toggleAnimation('open'); // right to left
console.log(
'next step',
`next: ${step.index}`,
`active: ${this.activeStep.index}`
);
// step.direction = 1;
await step.toggleAnimation('close');
// step.direction = -1;
}
} else if (
this.orientation == 'vertical' &&
this.verticalAnimation == 'grow'
) {
this.activeStep.toggleAnimation('close');
step.toggleAnimation('open');
} else {
step.toggleAnimation('open');
}

const allowed = this.emitEvent('igcActiveStepChanging', args);

if (!allowed) {
Expand Down Expand Up @@ -382,6 +467,9 @@ export default class IgcStepperComponent extends EventEmitterMixin<
private syncProperties(): void {
this.steps.forEach((step: IgcStepComponent, index: number) => {
step.orientation = this.orientation;
step.animationDuration = this.animationDuration;
step.horizontalAnimation = this.horizontalAnimation;
step.verticalAnimation = this.verticalAnimation;
step.stepType = this.stepType;
step.titlePosition = this.titlePosition;
step.contentTop = this.contentTop;
Expand Down
15 changes: 11 additions & 4 deletions src/components/stepper/themes/step/light/step.base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,12 @@
}

[part='content'] {
display: var(--horizontal-content-disply, none);
height: 100%;
overflow: auto;
overflow: hidden;
pointer-events: none;

&[aria-hidden='true'] {
height: 0;
}
}

[part~='top'] {
Expand Down Expand Up @@ -385,6 +388,7 @@
:host([active]) {
[part~='body'] {
display: var(--vertical-body-disply, block);
height: auto;
}

[part~='body bottom'] {
Expand All @@ -394,8 +398,11 @@
}

[part='content'] {
display: var(--horizontal-content-disply, block);
pointer-events: all;

&[aria-hidden='false'] {
height: auto;
}
}

[part~='indicator'] {
Expand Down
8 changes: 8 additions & 0 deletions src/components/stepper/themes/stepper/stepper.base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@
--horizontal-separator-display--first-of-type: none;
}

:host([orientation='vertical']) {
--vertical-body-disply: auto;

::slotted(igc-step:last-of-type) {
--vertical-body-disply--last-of-type: 0;
}
}

// LAST STEP
::slotted(igc-step:last-of-type) {
--vertical-body-disply--last-of-type: none;
Expand Down
Loading