Skip to content

Commit

Permalink
feat(client): add useAnimated hook, fix useOnScreen
Browse files Browse the repository at this point in the history
  • Loading branch information
Jozwiaczek committed Mar 4, 2021
1 parent f1a8b44 commit d7d338d
Show file tree
Hide file tree
Showing 9 changed files with 228 additions and 144 deletions.
89 changes: 48 additions & 41 deletions packages/client/src/containers/Login/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { useForm } from 'react-hook-form';
import { useHistory } from 'react-router-dom';

import {
Animated,
AnimatedLogo,
BackgroundSideLogo,
Checkbox,
Expand All @@ -13,6 +12,7 @@ import {
TextField,
} from '../../elements';
import { useAuth, useSnackbar } from '../../hooks';
import useAnimated from '../../hooks/useAnimated';
import {
ActionsContainer,
LinksContainer,
Expand All @@ -27,6 +27,14 @@ const Login = () => {
const history = useHistory();
const showSnackbar = useSnackbar();
const [loading, setLoading] = useState(false);
const animatedCard = useAnimated({ type: 'fadeIn' });
const { triggerAnimation } = useAnimated({
type: 'shake',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
targets: animatedCard.ref.current,
opt: { autoTrigger: false },
});
const { register, handleSubmit, errors, reset, trigger } = useForm<LoginInputs>({
mode: 'onBlur',
});
Expand All @@ -36,6 +44,7 @@ const Login = () => {
}

const onSubmit = async (values: LoginInputs) => {
triggerAnimation();
setLoading(true);
const isValid = await trigger();

Expand Down Expand Up @@ -63,46 +72,44 @@ const Login = () => {
return (
<LayoutContainer>
<BackgroundSideLogo />
<Animated>
<StyledCard>
<AnimatedLogo />
<Form
onSubmit={handleSubmit(onSubmit)}
errors={errors}
register={register}
loading={loading}
>
<TextField
autoFocus
required
name="email"
placeholder="Enter your email"
startAdornment={<StyledEmailIcon />}
/>
<TextField
required
name="password"
type="password"
placeholder="Enter your password"
autoComplete="current-password"
/>
<ActionsContainer>
<Checkbox name="keepMeLoggedIn" />
<StyledButton type="submit" fullWidth disabled={loading} withArrow>
Log in
</StyledButton>
</ActionsContainer>
<LinksContainer>
<Link to="/" colorVariant="grey">
Forgot password?
</Link>
<Link to="/registration" colorVariant="colour">
I don’t have an account
</Link>
</LinksContainer>
</Form>
</StyledCard>
</Animated>
<StyledCard ref={animatedCard.ref}>
<AnimatedLogo />
<Form
onSubmit={handleSubmit(onSubmit)}
errors={errors}
register={register}
loading={loading}
>
<TextField
autoFocus
required
name="email"
placeholder="Enter your email"
startAdornment={<StyledEmailIcon />}
/>
<TextField
required
name="password"
type="password"
placeholder="Enter your password"
autoComplete="current-password"
/>
<ActionsContainer>
<Checkbox name="keepMeLoggedIn" />
<StyledButton type="submit" fullWidth disabled={loading} withArrow>
Log in
</StyledButton>
</ActionsContainer>
<LinksContainer>
<Link to="/" colorVariant="grey">
Forgot password?
</Link>
<Link to="/registration" colorVariant="colour">
I don’t have an account
</Link>
</LinksContainer>
</Form>
</StyledCard>
</LayoutContainer>
);
};
Expand Down
17 changes: 0 additions & 17 deletions packages/client/src/elements/Animated/Animated.stories.tsx

This file was deleted.

7 changes: 0 additions & 7 deletions packages/client/src/elements/Animated/Animated.types.d.ts

This file was deleted.

73 changes: 0 additions & 73 deletions packages/client/src/elements/Animated/index.tsx

This file was deleted.

1 change: 0 additions & 1 deletion packages/client/src/elements/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export { default as Animated } from './Animated';
export { default as AnimatedLogo } from './AnimatedLogo';
export { default as BackgroundSideLogo } from './BackgroundSideLogo';
export { default as Button } from './Button';
Expand Down
72 changes: 72 additions & 0 deletions packages/client/src/hooks/useAnimated/getAnimation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import anime from 'animejs';

import { AnimationType, GetAnimationOptions } from './useAnimated.typed';

const getAnimation = (
type: AnimationType,
{ targets, duration, delay, maxDebounceSize }: GetAnimationOptions,
autoplay = true,
) => {
const baseConfig = {
targets,
duration,
autoplay,
delay,
};

const createAnimation = ({
duration: baseDuration = 500,
delay: baseDelay = 0,
...rest
}: anime.AnimeParams) =>
anime({
...baseConfig,
duration: baseDuration,
delay: baseDelay,
...rest,
});

switch (type) {
case 'shake': {
const internalMaxDebounceSize = maxDebounceSize || 16;
return createAnimation({
easing: 'easeInOutSine',
translateX: [
internalMaxDebounceSize * -1,
internalMaxDebounceSize,
internalMaxDebounceSize / -2,
internalMaxDebounceSize / 2,
0,
],
});
}
case 'fadeIn':
return createAnimation({
opacity: [0, 1],
easing: 'cubicBezier(.5, .05, .1, .3)',
});
case 'fadeOut':
return createAnimation({
opacity: [1, 0],
easing: 'cubicBezier(.5, .05, .1, .3)',
});
case 'slideInUp': {
return createAnimation({
translateY: ['100%', 0],
easing: 'cubicBezier(0.68, -0.6, 0.32, 1.6)',
});
}
case 'slideInOut':
return createAnimation({
translateY: [0, '100%'],
easing: 'cubicBezier(0.68, -0.6, 0.32, 1.6)',
});
default:
return createAnimation({
opacity: [0, 1],
easing: 'cubicBezier(.5, .05, .1, .3)',
});
}
};

export default getAnimation;
49 changes: 49 additions & 0 deletions packages/client/src/hooks/useAnimated/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { AnimeInstance } from 'animejs';
import { useLayoutEffect, useRef } from 'react';

import useOnScreen from '../useOnScreen';
import getAnimation from './getAnimation';
import { AnimationType, UseAnimatedProps, UseAnimatedReturnProps } from './useAnimated.typed';

const useAnimated = ({ targets, type, opt = {} }: UseAnimatedProps): UseAnimatedReturnProps => {
const { root, rootMargin, animationOpt, autoTrigger = true, autoTriggerOnce = false } = opt;
const isInOut = Array.isArray(type);
const animateInType = (isInOut ? type[0] : type) as AnimationType;
const animateOutType = isInOut ? (type[1] as AnimationType) : undefined;

const animateInRef = useRef<AnimeInstance>();
const animateOutRef = useRef<AnimeInstance>();
const containerRefInner = useRef<Element>(null);
const isInViewPort = useOnScreen(containerRefInner, {
root,
rootMargin,
triggerOnce: autoTriggerOnce,
disabled: !autoTrigger,
});

useLayoutEffect(() => {
const innerTargets = targets || containerRefInner.current;
const options = {
targets: innerTargets,
...animationOpt,
};
if (!autoTrigger) {
animateInRef.current = getAnimation(animateInType, options, false);
}

if (autoTrigger && isInViewPort) {
animateInRef.current = getAnimation(animateInType, options);
}
if (autoTrigger && !isInViewPort && animateOutType) {
animateOutRef.current = getAnimation(animateOutType, options);
}
}, [animateInType, animateOutType, animationOpt, autoTrigger, isInViewPort, targets]);

const triggerAnimation = () => {
animateInRef.current?.play();
};

return { triggerAnimation, ref: containerRefInner };
};

export default useAnimated;
36 changes: 36 additions & 0 deletions packages/client/src/hooks/useAnimated/useAnimated.typed.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Ref } from 'react';

export type AnimationType = 'fadeIn' | 'fadeOut' | 'shake' | 'slideInUp' | 'slideInOut';
export type UseAnimatedType = AnimationType | [AnimationType, AnimationType];

export type AnimationTargets = Element | ReadonlyArray<Element>;

export interface AnimationOptions {
maxDebounceSize?: number;
duration?: number | FunctionBasedParameter;
delay?: number | FunctionBasedParameter;
}

export interface GetAnimationOptions extends AnimationOptions {
targets: AnimationTargets | null;
}

export interface UseAnimatedOptions {
root?: Element | null;
rootMargin?: string;
autoTrigger?: boolean;
autoTriggerOnce?: boolean;
animationOpt?: AnimationOptions;
}

export interface UseAnimatedProps {
targets?: AnimationTargets;
type: UseAnimatedType;
opt?: UseAnimatedOptions;
}

export interface UseAnimatedReturnProps {
triggerAnimation: () => void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
ref: Ref<any>;
}
Loading

0 comments on commit d7d338d

Please sign in to comment.