Skip to content

Commit

Permalink
feat: app loader component
Browse files Browse the repository at this point in the history
  • Loading branch information
avlyalin committed Jun 1, 2020
1 parent f3be077 commit f2a548f
Show file tree
Hide file tree
Showing 14 changed files with 331 additions and 33 deletions.
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
"@fortawesome/free-solid-svg-icons": "^5.13.0",
"@fortawesome/react-fontawesome": "^0.1.9",
"@hot-loader/react-dom": "^16.13.0",
"animate.css": "^4.1.0",
"classnames": "^2.2.6",
"core-js": "^3.6.5",
"firebase": "^7.14.1",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/components/loader/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Loader } from './loader';
28 changes: 28 additions & 0 deletions src/components/loader/loader.ispec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const customConfig = {
failureThreshold: 0.01,
failureThresholdType: 'percent',
};

describe('<Loader /> visually looks correct', () => {
test('prop: is loading', async () => {
await page.goto(
'http://localhost:9009/iframe.html?id=components-loader--common&knob-Message=&knob-Animation=false&knob-Is%20loading=true',
);
const image = await page.screenshot();
expect(image).toMatchImageSnapshot(customConfig);
});
test('prop: not loading', async () => {
await page.goto(
'http://localhost:9009/iframe.html?id=components-loader--common&knob-Message=application%20start&knob-Animation=false&knob-Is%20loading=false',
);
const image = await page.screenshot();
expect(image).toMatchImageSnapshot(customConfig);
});
test('prop: is loading with text', async () => {
await page.goto(
'http://localhost:9009/iframe.html?id=components-loader--common&knob-Message=application%20start&knob-Animation=false&knob-Is%20loading=true',
);
const image = await page.screenshot();
expect(image).toMatchImageSnapshot(customConfig);
});
});
34 changes: 34 additions & 0 deletions src/components/loader/loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import { Logo } from './logo';
import { Text } from './text';

const Loader = ({ isLoading = false, animation = true, text = '' }) => {
return (
isLoading && (
<div
className={classnames(
'fixed z-50',
'h-screen w-screen',
'xs:px-1 sm:px-4',
'space-y-3 xs:space-y-5 sm:space-y-8',
'flex flex-col justify-center items-center',
'bg-white',
'overflow-hidden',
)}
>
<Logo animation={animation} />
<Text text={text} />
</div>
)
);
};

Loader.propTypes = {
isLoading: PropTypes.bool.isRequired,
text: PropTypes.string,
animation: PropTypes.bool,
};

export { Loader };
17 changes: 17 additions & 0 deletions src/components/loader/loader.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';
import { withKnobs, text, boolean } from '@storybook/addon-knobs';
import { containerDecorator } from '_storybook/container';
import { Loader } from './loader';

// eslint-disable-next-line import/no-default-export
export default {
title: 'Components/Loader',
decorators: [containerDecorator({ style: { padding: 0 } }), withKnobs],
};

export const Common = () => {
const isLoading = boolean('Is loading', true);
const message = text('Message', 'Запуск приложения');
const animation = boolean('Animation', true);
return <Loader isLoading={isLoading} text={message} animation={animation} />;
};
119 changes: 119 additions & 0 deletions src/components/loader/logo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import React, { useEffect, useState } from 'react';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import styles from './logo.module.css';

const Logo = ({ animation = true }) => {
const [letterIndex, setLetterIndex] = useState(0);

useEffect(() => {
const interval = setInterval(() => {
const newLetterIndex = (letterIndex + 1) % 'codenames'.length;
setLetterIndex(newLetterIndex);
}, 1500);
return () => {
clearInterval(interval);
};
}, [letterIndex]);

return (
<div className={classnames('w-full text-center')}>
<Letter
letter={'c'}
color={'blue'}
classes={classnames({
[styles.letterAnimationFlip]: animation && letterIndex === 0,
})}
/>
<Letter
letter={'o'}
color={'red'}
classes={classnames({
[styles.letterAnimationFlip]: animation && letterIndex === 1,
})}
/>
<Letter
letter={'d'}
color={'blue'}
classes={classnames({
[styles.letterAnimationFlip]: animation && letterIndex === 2,
})}
/>
<Letter
letter={'e'}
color={'blue'}
classes={classnames({
[styles.letterAnimationFlip]: animation && letterIndex === 3,
})}
/>
<Letter
letter={'n'}
color={'red'}
classes={classnames({
[styles.letterAnimationFlip]: animation && letterIndex === 4,
})}
/>
<Letter
letter={'a'}
color={'red'}
classes={classnames({
[styles.letterAnimationFlip]: animation && letterIndex === 5,
})}
/>
<Letter
letter={'m'}
color={'blue'}
classes={classnames({
[styles.letterAnimationFlip]: animation && letterIndex === 6,
})}
/>
<Letter
letter={'e'}
color={'red'}
classes={classnames({
[styles.letterAnimationFlip]: animation && letterIndex === 7,
})}
/>
<Letter
letter={'s'}
color={'black'}
classes={classnames({
[styles.letterAnimationHinge]: animation && letterIndex === 8,
})}
/>
</div>
);
};

Logo.propTypes = {
animation: PropTypes.bool.isRequired,
};

const Letter = ({ letter, color, classes }) => {
return (
<span
className={classnames(
classes,
styles.letter,
'h-9 xs:h-10 w-7 xs:w-9 inline-flex justify-center items-center',
'text-white font-medium uppercase text-2xl xs:text-3xl',
'rounded-md xs:rounded-lg',
{
'bg-blue-100': color === 'blue',
'bg-red-100': color === 'red',
'bg-black': color === 'black',
},
)}
>
{letter}
</span>
);
};

Letter.propTypes = {
letter: PropTypes.string.isRequired,
color: PropTypes.oneOf(['blue', 'red', 'black']).isRequired,
classes: PropTypes.string,
};

export { Logo };
53 changes: 53 additions & 0 deletions src/components/loader/logo.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
@import "animate.css/source/flippers/flip.css";

.letter {
margin-left: 0.35rem;
}

@responsive {
@screen xs {
.letter {
margin-left: 0.29rem;
}
}
@screen sm {
.letter {
margin-left: 0.35rem;
}
}
}

.letter_animation_flip {
animation: flip;
animation-duration: 1.5s;
}

.letter_animation_hinge {
transform-origin: top right;
animation: fall;
animation-duration: 1.5s;
}

@keyframes fall {
0% {
animation-timing-function: ease-in-out;
}

20%,
60% {
transform: rotate3d(0, 0, 1, -40deg);
animation-timing-function: ease-in-out;
}

40%,
80% {
transform: rotate3d(0, 0, 1, -25deg);
animation-timing-function: ease-in-out;
opacity: 1;
}

to {
transform: translate3d(0, 300px, 0);
opacity: 0;
}
}
23 changes: 23 additions & 0 deletions src/components/loader/text.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';

const Text = ({ text = '' }) => {
return (
text && (
<span
className={classnames(
'text-2xl font-semibold tracking-wide text-gray-400 text-center',
)}
>
{text}
</span>
)
);
};

Text.propTypes = {
text: PropTypes.string.isRequired,
};

export { Text };
Loading

0 comments on commit f2a548f

Please sign in to comment.