Skip to content

Commit

Permalink
feat(core-components-confirmation): fixes (#311)
Browse files Browse the repository at this point in the history
* feat(core-components-confirmation): add more css-vars, fix vars values

* feat(core-components-confirmation): add click theme

* docs(core-components-confirmation): refactoring story

* feat(core-components-confirmation): refactoring timers

* docs(core-components-confirmation): change countdown duration

* feat(core-components-confirmation): add timepassed min width

* feat(core-components-confirmation): add error input shaking

* refactor(core-components-confirmation): add prefix to mixins

Co-authored-by: reme3d2y <AYatsenko@alfabank.ru>
Co-authored-by: Alex Yatsenko <reme3d2y@gmail.com>
  • Loading branch information
3 people committed Oct 13, 2020
1 parent d05a0ef commit e56a137
Show file tree
Hide file tree
Showing 12 changed files with 153 additions and 119 deletions.
38 changes: 23 additions & 15 deletions packages/confirmation/src/component.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import { name, version } from '../package.json';
import { Select } from '../../select/src/Component';


<Meta title='Компоненты' component={Confirmation} />
<Meta
title='Компоненты'
component={Confirmation}
parameters={{ 'theme-switcher': { themes: ['click', 'mobile'] } }}
/>


<!-- Canvas -->
Expand All @@ -34,16 +38,20 @@ import { Select } from '../../select/src/Component';
} else if (variant === 'fatal') {
setError({...error, 'fatal': 'Выполните операцию с самого начала'});
}
}, 1500);
}, 1000);
}
const handleSmsRetryClick = (variant) => {
setCodeSending({...codeSending, [variant]: true});
setError({...error, [variant]: ''});
setCode(variant, '')
setTimeout(() => {
setCodeSending({...codeSending, [variant]: false});
}, 1500);
}, 1000);
}
const alignContent = select('alignContent', ['center', 'left'], 'center');
const phone = '+7 000 000 00 42';
const countdownDuration = 10000;
const requiredCharAmount = number('requiredCharAmount', 5);
return (
<div>
<Select
Expand Down Expand Up @@ -71,51 +79,51 @@ import { Select } from '../../select/src/Component';
}}
>
<Confirmation
phone='+7 000 000 00 42'
phone={phone}
onInputFinished={() => handleSubmit('success')}
onSmsRetryClick={() => handleSmsRetryClick('success')}
codeSending={codeSending['success']}
codeChecking={codeChecking['success']}
countdownDuration={10000}
countdownDuration={countdownDuration}
code={value['success']}
onInputChange={({ code }) => setCode('success', code)}
alignContent={select('alignContent', ['center', 'left'], 'center')}
requiredCharAmount={number('requiredCharAmount', 5)}
alignContent={alignContent}
requiredCharAmount={requiredCharAmount}
/>
</div>
}
{variant[0].value === 'error' &&
<div style={{display: 'flex', width: '100%', maxWidth: '375px', padding: '15px', boxSizing: 'border-box'}}>
<Confirmation
phone='+7 000 000 00 42'
phone={phone}
onInputFinished={( ) => handleSubmit('error')}
onSmsRetryClick={() => handleSmsRetryClick('error')}
codeSending={codeSending['error']}
codeChecking={codeChecking['error']}
error={error['error']}
countdownDuration={10000}
countdownDuration={countdownDuration}
code={value['error']}
onInputChange={({ code }) => setCode('error', code)}
alignContent={select('alignContent', ['center', 'left'], 'left')}
requiredCharAmount={number('requiredCharAmount', 5)}
alignContent={alignContent}
requiredCharAmount={requiredCharAmount}
/>
</div>
}
{variant[0].value === 'fatal' &&
<div style={{display: 'flex', width: '100%', maxWidth: '375px', padding: '15px', boxSizing: 'border-box'}}>
<Confirmation
phone='+7 000 000 00 42'
phone={phone}
onInputFinished={() => handleSubmit('fatal')}
onSmsRetryClick={() => handleSmsRetryClick('fatal')}
codeSending={codeSending['fatal']}
codeChecking={codeChecking['fatal']}
error={error['fatal']}
errorIsFatal={true}
countdownDuration={10000}
countdownDuration={countdownDuration}
code={value['fatal']}
onInputChange={({ code }) => setCode('fatal', code)}
alignContent={select('alignContent', ['center', 'left'], 'left')}
requiredCharAmount={number('requiredCharAmount', 5)}
alignContent={alignContent}
requiredCharAmount={requiredCharAmount}
/>
</div>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,11 @@ export const CodeInput = forwardRef<HTMLInputElement, CodeInputProps>(
}, [value.length, prevValue.length, slotsCount, focus]);

return (
<div className={cn(styles.component, className)}>
<div
className={cn(styles.component, className, {
[styles.shake]: Boolean(error),
})}
>
{new Array(slotsCount).fill('').map((_, index) => (
<Input
value={value}
Expand Down
31 changes: 27 additions & 4 deletions packages/confirmation/src/components/code-input/index.module.css
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
@import '../../../../themes/src/default.css';
@import '../../vars.css';

.component {
display: flex;
}

.input {
@mixin promo_small;

flex-shrink: 0;
width: 32px;
width: 36px;
height: 48px;
margin-right: var(--gap-xs);
padding: 0;
Expand All @@ -20,6 +17,9 @@
outline: none;
color: var(--confirmation-input-text-color);
text-align: center;
font-size: var(--confirmation-input-font-size);
font-weight: var(--confirmation-input-font-weight);
font-family: var(--confirmation-input-font-family);

&:last-child {
margin-right: var(--gap-xs);
Expand All @@ -28,5 +28,28 @@
&.hasError {
color: var(--confirmation-error-color);
caret-color: var(--confirmation-input-text-color);
background-color: var(--confirmation-input-error-bg-color);
}
}

@keyframes shake {
0% {
transform: translateX(0);
}

25% {
transform: translateX(-2px);
}

75% {
transform: translateX(2px);
}

100% {
transform: translateX(0);
}
}

.shake {
animation: shake 0.15s linear 0s 3;
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React, { FC, useState, useRef, useCallback, useEffect } from 'react';
import React, { FC } from 'react';

import styles from './index.module.css';

type Props = {
duration: number;
progress: number; // 0-1
className: string;
};

Expand All @@ -13,31 +13,8 @@ const RADIUS = SIZE / 2;

const FULL_TURN = Math.PI * 2;

export const CountdownLoader: FC<Props> = ({ duration, className }) => {
const [angle, setAngle] = useState(0);

const start = useRef(Date.now());
const requestId = useRef(0);

const updateProgress = useCallback(() => {
const progress = (Date.now() - start.current) / duration;

const newAngle = progress < 1 ? progress * FULL_TURN : FULL_TURN;

setAngle(newAngle);

if (progress < 1) {
requestId.current = window.requestAnimationFrame(updateProgress);
}
}, [duration]);

useEffect(() => {
requestId.current = window.requestAnimationFrame(updateProgress);

return () => {
window.cancelAnimationFrame(requestId.current);
};
}, [updateProgress]);
export const CountdownLoader: FC<Props> = ({ progress, className }) => {
const angle = progress < 1 ? progress * FULL_TURN : FULL_TURN;

const x = RADIUS - RADIUS * Math.sin(angle);
const y = RADIUS - RADIUS * Math.cos(angle);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
@import '../../../../themes/src/default.css';
@import '../../vars.css';

.circle {
Expand Down
89 changes: 32 additions & 57 deletions packages/confirmation/src/components/countdown/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ export function formatMsAsMinutes(ms: number) {
return `${paddedMinutes}:${paddedSeconds}`;
}

const TIMER_ITERATION_VALUE = 1000;

export type CountdownProps = {
duration: number;
hasPhoneMask: boolean;
Expand All @@ -63,74 +61,52 @@ export const Countdown: FC<CountdownProps> = ({
onCountdownFinished,
className,
}) => {
const [timer, setTimer] = useState(duration);

const [repeatSmsButtonShow, setRepeatSmsButtonShow] = useState(false);

const timerId = useRef<number>(0);

const stopTimer = () => {
if (timerId.current) {
clearTimeout(timerId.current);
timerId.current = 0;
}
};

const tic = useCallback(() => {
if (timer <= 0) {
stopTimer();

setTimer(0);
setRepeatSmsButtonShow(true);

if (onCountdownFinished) {
onCountdownFinished();
}
} else {
setTimer(prevTimer => prevTimer - TIMER_ITERATION_VALUE);

timerId.current = window.setTimeout(tic, TIMER_ITERATION_VALUE);
}
}, [onCountdownFinished, timer]);
const requestId = useRef(0);

const startTimer = useCallback(() => {
stopTimer();
const start = useRef(0);

timerId.current = window.setTimeout(tic, TIMER_ITERATION_VALUE);
}, [tic]);

const startSmsCountdown = useCallback(() => {
setTimer(duration);
setRepeatSmsButtonShow(false);
const [repeatSmsButtonShow, setRepeatSmsButtonShow] = useState(false);

startTimer();
}, [duration, startTimer]);
const [timePassed, setTimePassed] = useState(0);

const handleRepeatSmsButtonClick = useCallback(
(event: MouseEvent) => {
startSmsCountdown();
setRepeatSmsButtonShow(false);

if (onRepeatSms) {
onRepeatSms(event);
}
},
[onRepeatSms, startSmsCountdown],
[onRepeatSms],
);

const updateProgress = useCallback(() => {
const passed = Date.now() - start.current;

setTimePassed(passed);

if (passed < duration) {
requestId.current = window.requestAnimationFrame(updateProgress);
} else {
setRepeatSmsButtonShow(true);

if (onCountdownFinished) {
onCountdownFinished();
}
}
}, [duration, onCountdownFinished]);

useEffect(() => {
startTimer();
start.current = Date.now();

requestId.current = window.requestAnimationFrame(updateProgress);

return () => {
stopTimer();
window.cancelAnimationFrame(requestId.current);
};
}, [startTimer]);
}, [updateProgress, repeatSmsButtonShow]);

useEffect(() => {
if (!repeatSmsButtonShow && !timerId.current) {
// если компонент переключился в активное состояние, а таймер еще не запущен, то стартуем его
startTimer();
}
}, [repeatSmsButtonShow, startTimer]);
const progress = timePassed / duration;

return (
<div className={cn(styles.component, styles[alignContent], className)}>
Expand All @@ -151,12 +127,11 @@ export const Countdown: FC<CountdownProps> = ({
<div className={styles.info}>Запросить повторно можно через</div>

<div className={styles.loaderWrap}>
<CountdownLoader
duration={duration + TIMER_ITERATION_VALUE}
className={styles.loader}
/>
<CountdownLoader progress={progress} className={styles.loader} />

{formatMsAsMinutes(timer)}
<div className={styles.timePassed}>
{formatMsAsMinutes(duration - timePassed)}
</div>
</div>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
@import '../../../../themes/src/default.css';
@import '../../vars.css';

.component {
@mixin paragraph_primary_medium;
@mixin _confirmation-text;

display: flex;
flex-direction: column;
Expand All @@ -27,3 +26,8 @@
justify-content: center;
}
}

.timePassed {
display: flex;
min-width: 3em;
}

0 comments on commit e56a137

Please sign in to comment.