Skip to content

Commit

Permalink
refactor: add chevrons to onboarding screens (#435)
Browse files Browse the repository at this point in the history
  • Loading branch information
ItsANameToo authored Jul 11, 2024
1 parent ad48646 commit 3b95d01
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 59 deletions.
3 changes: 3 additions & 0 deletions src/assets/icons/chevron-left.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/icons/chevron-right.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
84 changes: 68 additions & 16 deletions src/pages/Onboarding.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import { ReactNode, useEffect, useState } from 'react';
import { ReactNode, useEffect, useRef, useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';

import cn from 'classnames';
import { useNavigate } from 'react-router-dom';
import { Trans, useTranslation } from 'react-i18next';
import constants from '@/constants';
import { ShortcutIcon } from '@/shared/components/icon/illustration/ShortcutIcon';
import {
Button,
ControlConnectionsIcon,
FingerPrintIcon,
Header,
Heading,
Icon,
ProgressBar,
TransactionsPassphraseIcon,
} from '@/shared/components';
import { ShortcutIcon } from '@/shared/components/icon/illustration/ShortcutIcon';
import { useOs } from '@/lib/hooks/useOs';
import constants from '@/constants';

type OnboardingScreen = {
id: number;
Expand All @@ -27,11 +29,14 @@ const Onboarding = () => {

const navigate = useNavigate();

const [activeOnboardingScreen, setActiveOnboardingScreen] = useState<number>(0);
const interval = useRef<ReturnType<typeof setInterval> | undefined>();

const [activeIndex, setActiveIndex] = useState<number>(0);
const [filledSegments, setFilledSegments] = useState<boolean[]>(Array(4).fill(false));

const onboardingScreens: OnboardingScreen[] = [
{
id: 1,
id: 0,
illustration: <FingerPrintIcon />,
heading: (
<Heading level={3} className='w-[256px] text-center'>
Expand All @@ -40,7 +45,7 @@ const Onboarding = () => {
),
},
{
id: 2,
id: 1,
illustration: <ControlConnectionsIcon />,
heading: (
<Heading level={3} className='w-[297px] text-center'>
Expand All @@ -49,7 +54,7 @@ const Onboarding = () => {
),
},
{
id: 3,
id: 2,
illustration: <TransactionsPassphraseIcon />,
heading: (
<Heading level={3} className='w-[257px] text-center'>
Expand All @@ -58,7 +63,7 @@ const Onboarding = () => {
),
},
{
id: 4,
id: 3,
illustration: <ShortcutIcon />,
heading: (
<Heading level={3} className='w-[300px] text-center'>
Expand All @@ -75,27 +80,74 @@ const Onboarding = () => {
];

useEffect(() => {
const interval = setInterval(() => {
setActiveOnboardingScreen((prevIndex) => (prevIndex + 1) % onboardingScreens.length);
interval.current = setInterval(() => {
goToNextScreen();
}, 5000);
return () => {
clearInterval(interval);
clearInterval(interval.current);
};
}, []);

const resetInterval = () => {
clearInterval(interval.current);
interval.current = setInterval(() => {
goToNextScreen();
}, 5000);
};

const goToNextScreen = () => {
setActiveIndex((prevIndex) => {
const newIndex = (prevIndex + 1) % onboardingScreens.length;
const newFilledSegments = Array(onboardingScreens.length)
.fill(false)
.map((_, idx) => idx <= newIndex);
setFilledSegments(newFilledSegments);
return newIndex;
});
resetInterval();
};

const goToPreviousScreen = () => {
setActiveIndex((prevIndex) => {
const newIndex = (prevIndex - 1 + onboardingScreens.length) % onboardingScreens.length;
const newFilledSegments = Array(onboardingScreens.length)
.fill(false)
.map((_, idx) => idx <= newIndex);
setFilledSegments(newFilledSegments);
return newIndex;
});
resetInterval();
};

return (
<div className='fade pt-[58px] duration-1000 ease-in-out'>
<Header />
<ProgressBar itemsLength={onboardingScreens.length} />
<ProgressBar activeIndex={activeIndex} filledSegments={filledSegments} />
<div className='relative h-[410px]'>
<div className='absolute left-4 top-1/2 z-1'>
<button
onClick={() => goToPreviousScreen()}
className='h-6 w-6 rounded-full text-theme-secondary-500 transition hover:bg-theme-secondary-100 hover:text-black dark:text-theme-secondary-300 dark:hover:bg-theme-secondary-700 dark:hover:text-white'
>
<Icon icon='chevron-left' className='h-6 w-6' />
</button>
</div>
<div className='absolute right-4 top-1/2 z-1'>
<button
onClick={() => goToNextScreen()}
className='h-6 w-6 rounded-full text-theme-secondary-500 transition hover:bg-theme-secondary-100 hover:text-black dark:text-theme-secondary-300 dark:hover:bg-theme-secondary-700 dark:hover:text-white'
>
<Icon icon='chevron-right' className='h-6 w-6' />
</button>
</div>
{onboardingScreens.map((screen, index) => (
<div
className={cn(
'absolute left-0 top-[70px] flex w-full items-center justify-center gap-6 px-9 transition-all duration-1000 ease-in-out',
{
'translate-x-0 opacity-100': activeOnboardingScreen === index,
'-translate-x-full opacity-0': activeOnboardingScreen > index,
'translate-x-full opacity-0': activeOnboardingScreen < index,
'translate-x-0 opacity-100': activeIndex === index,
'-translate-x-full opacity-0': activeIndex > index,
'translate-x-full opacity-0': activeIndex < index,
},
)}
key={screen.id}
Expand Down
26 changes: 26 additions & 0 deletions src/shared/components/icon/index.generated.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export type IconDefinition =
| 'arrow-left'
| 'arrow-right'
| 'check'
| 'chevron-left'
| 'chevron-right'
| 'clear'
| 'clock'
| 'code'
Expand Down Expand Up @@ -150,6 +152,8 @@ export const availableIcons: IconDefinition[] = [
'arrow-left',
'arrow-right',
'check',
'chevron-left',
'chevron-right',
'clear',
'clock',
'code',
Expand Down Expand Up @@ -404,6 +408,28 @@ export const IconSvg = {
/>
</svg>
),
'chevron-left': (
<svg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24'>
<path
stroke='currentColor'
strokeLinecap='round'
strokeLinejoin='round'
strokeWidth='1.667'
d='m14.5 7-5 5 5 5'
/>
</svg>
),
'chevron-right': (
<svg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24'>
<path
stroke='currentColor'
strokeLinecap='round'
strokeLinejoin='round'
strokeWidth='1.667'
d='m9.5 17 5-5-5-5'
/>
</svg>
),
clear: (
<svg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'>
<path
Expand Down
72 changes: 29 additions & 43 deletions src/shared/components/progress/ProgressBar.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,40 @@
import { useEffect, useState } from 'react';
import cn from 'classnames';

export const ProgressBar = ({ itemsLength }: { itemsLength: number }) => {
const [activeBarIndex, setActiveBarIndex] = useState<number>(0);
const [filledSegments, setFilledSegments] = useState<boolean[]>(Array(itemsLength).fill(false));
export const ProgressBar = ({
activeIndex,
filledSegments,
}: {
activeIndex: number;
filledSegments: boolean[];
}) => {
const [animateIndex, setAnimateIndex] = useState<number | null>(null);

useEffect(() => {
if (activeBarIndex !== 0) return;
const timeout = setTimeout(() => {
setFilledSegments([true, ...Array(itemsLength - 1).fill(false)]);
}, 300);
setAnimateIndex(activeIndex);
}, [activeIndex]);

return () => {
clearTimeout(timeout);
};
}, [activeBarIndex]);
const bars = filledSegments.map((isFilled, index) => {
const shouldAnimate = index === animateIndex;

useEffect(() => {
const intervalId = setInterval(() => {
if (activeBarIndex === filledSegments.length - 1) {
setFilledSegments(Array(itemsLength).fill(false));
setActiveBarIndex(0);
return;
}
const newArray = [...filledSegments];
newArray[activeBarIndex + 1] = true;
setFilledSegments(newArray);
setActiveBarIndex((prevIndex) => (prevIndex + 1) % itemsLength);
}, 4900);

return () => clearInterval(intervalId);
}, [filledSegments, itemsLength]);

const bars = filledSegments.map((isFilled, index) => (
<div
key={index}
className='relative h-1.25 w-full overflow-hidden rounded-2.5xl bg-theme-secondary-200 dark:bg-theme-secondary-600'
>
return (
<div
className={cn('h-full rounded-3xl bg-theme-primary-700 dark:bg-theme-primary-650', {
'w-full': isFilled,
'w-0': !isFilled,
})}
style={{
transition:
activeBarIndex === index && isFilled ? 'width 5s ease-in-out' : 'unset',
}}
/>
</div>
));
key={index}
className='relative h-1.25 w-full overflow-hidden rounded-2.5xl bg-theme-secondary-200 dark:bg-theme-secondary-600'
>
<div
className={cn(
'h-full rounded-3xl bg-theme-primary-700 dark:bg-theme-primary-650',
{
'w-full': isFilled,
'w-0': !isFilled,
'animate-fillProgress': shouldAnimate,
},
)}
/>
</div>
);
});

return <div className='flex gap-2'>{bars}</div>;
};
5 changes: 5 additions & 0 deletions tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,18 @@ export default {
'0%': { transform: 'translateY(100%)' },
'100%': { transform: 'translateY(0%)' },
},
fillProgressBar: {
'0%': { width: '0%' },
'100%': { width: '100%' },
},
},
animation: {
slideUp: 'slideUp 1.2s ease-in-out 1.8s forwards',
fadeInTransformAndScale:
'fadeInAndTransform 0.8s ease-in-out forwards, scale 0.4s ease-in-out 1s forwards',
translateUp: 'translateUp 0.5s ease-in-out 2s forwards',
decreaseHeight: 'decreaseHeight 0.5s ease-in-out 2s forwards',
fillProgress: 'fillProgressBar 5s ease-in-out',
},
zIndex: {
1: '1',
Expand Down

0 comments on commit 3b95d01

Please sign in to comment.