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

Add a beautiful Loader component #1

Merged
merged 1 commit into from
Aug 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 19 additions & 4 deletions App.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React from 'react';
import React, { useState } from 'react';
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
import { StyleSheet, Text, View, Pressable } from 'react-native';

import { Provider as PaperProvider } from 'react-native-paper';
import { Provider as StoreProvider } from 'react-redux';

import { store } from 'Controllers';

import Logo from 'Components/Logo';
import Loader from 'Components/Loader';

const styles = StyleSheet.create({
container: {
Expand All @@ -16,14 +16,29 @@ const styles = StyleSheet.create({
alignItems: 'center',
justifyContent: 'center',
},
loader: {
height: 100,
width: 100,
},
});

export default function App() {
const [isLoading, setLoading] = useState(false);

function handlePress() {
setLoading((prevState) => !prevState);
}

return (
<StoreProvider store={store}>
<PaperProvider theme={store.getState().theme}>
<View style={styles.container}>
<Logo height={100} width={100} />
<Pressable onPress={handlePress}>
<Loader
isLoading={isLoading}
style={styles.loader}
/>
</Pressable>
<Text style={{ marginTop: 16 }}>
Welcome to Monk Software Development Kit!
</Text>
Expand Down
1 change: 1 addition & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ function babelConfig(api) {
},
plugins: [
['module-resolver', { root: ['./src'] }],
'react-native-reanimated/plugin',
],
};
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"react-dom": "16.13.1",
"react-native": "https://github.com/expo/react-native/archive/sdk-42.0.0.tar.gz",
franklinkemta marked this conversation as resolved.
Show resolved Hide resolved
"react-native-paper": "^4.9.2",
"react-native-reanimated": "~2.2.0",
"react-native-svg": "12.1.1",
"react-native-web": "~0.13.12",
"react-redux": "^7.2.4"
Expand Down
21 changes: 21 additions & 0 deletions src/Components/CustomPropTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import PropTypes from 'prop-types';

export function progress(props, propName, componentName) {
const value = props[propName];

const notANumber = typeof value !== 'number';
const notInTheRange = !notANumber && (value < 0 || value > 99);

if (notANumber || notInTheRange) {
return new Error(
`
Invalid prop \`${propName}\` supplied to\`${componentName}\`.
It must be a number between 0 and 99 included.
`,
);
}

return null;
}

export const nodeEnv = PropTypes.oneOf(['development', 'test', 'production']);
24 changes: 24 additions & 0 deletions src/Components/Loader/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';
import PropTypes from 'prop-types';
import Logo from 'Components/Logo';

/**
* Display a loader built with the Monk Logo
* @param isLoading
* @param logoProps {object} passed through <Logo />
* @returns {JSX.Element}
* @constructor
*/
function Loader({ isLoading, ...logoProps }) {
return <Logo animated={isLoading} {...logoProps} />;
}

Loader.propTypes = {
isLoading: PropTypes.bool,
};

Loader.defaultProps = {
isLoading: false,
};

export default Loader;
126 changes: 113 additions & 13 deletions src/Components/Logo/index.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,120 @@
import React, { useMemo } from 'react';
import React, { useEffect, useMemo } from 'react';
import PropTypes from 'prop-types';
import { Platform, StyleSheet } from 'react-native';
import { useTheme } from 'react-native-paper';
import Svg, { Circle, G } from 'react-native-svg';
import Svg, { Circle, G, Path } from 'react-native-svg';
import isEmpty from 'Functions/isEmpty';

import Animated, {
cancelAnimation,
Easing,
withRepeat,
withTiming,
useSharedValue,
useAnimatedProps,
} from 'react-native-reanimated';

const AnimatedPath = Animated.createAnimatedComponent(Path);

const NativeInnerCircle = ({ animated }) => {
const radius = useSharedValue(10);

const animatedProps = useAnimatedProps(() => {
// draw a circle
const path = `
M 50, 50
m -${radius.value}, 0
a ${radius.value},${radius.value} 0 1,0 ${radius.value * 2},0
a ${radius.value},${radius.value} 0 1,0 ${-radius.value * 2},0
`;
return {
d: path,
};
});

useEffect(() => {
if (animated) {
radius.value = withRepeat(withTiming(30, {
duration: 700,
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
}), -1, true);
} else {
cancelAnimation(radius);
radius.value = 10;
}
}, [animated, radius]);

return (
<AnimatedPath
animatedProps={animatedProps}
fill="black"
/>
);
};

NativeInnerCircle.propTypes = {
animated: PropTypes.bool,
};

NativeInnerCircle.defaultProps = {
animated: false,
};

const DefaultInnerCircle = ({ animated }) => (
<Circle
fill="#000"
cx="0"
cy="0"
r="10"
transform="translate(50 50)"
>
<animateTransform
attributeName="transform"
type="scale"
additive="sum"
begin="0s"
dur="1.5s"
repeatCount="indefinite"
calcMode="spline"
keySplines="0.4 0 0.2 1; 0.4 0 0.2 1"
values={`1; ${animated ? 3 : 1}; 1`}
/>
</Circle>
);

DefaultInnerCircle.propTypes = {
animated: PropTypes.bool,
};

DefaultInnerCircle.defaultProps = {
animated: false,
};

const InnerCircle = Platform.select({
native: () => NativeInnerCircle,
default: () => DefaultInnerCircle,
})();

const styles = StyleSheet.create({
root: {
height: 75,
width: 75,
},
});

/**
* Display Monk's SVG logo
* @param animated {bool}
* @param color {string}
* @param height {number}
* @param theme
* @param width {number}
* @param passThroughProps {object}
* @returns {JSX.Element}
* @constructor
*/
function Logo({ color, height, width }) {
function Logo({
animated,
color,
...passThroughProps
}) {
const { colors } = useTheme();

const stroke = useMemo(
Expand All @@ -24,9 +125,10 @@ function Logo({ color, height, width }) {
return (
<Svg
viewBox="0 0 100 100"
style={{ height, width }}
style={styles}
{...passThroughProps}
>
<G id="logo">
<G>
<Circle
stroke={stroke}
fill="transparent"
Expand All @@ -35,22 +137,20 @@ function Logo({ color, height, width }) {
cy="50"
r="38"
/>
<Circle fill="#000" cx="50" cy="50" r="11" />
<InnerCircle animated={animated} />
</G>
</Svg>
);
}

Logo.propTypes = {
animated: PropTypes.bool,
color: PropTypes.string,
height: PropTypes.number,
width: PropTypes.number,
};

Logo.defaultProps = {
animated: false,
color: '',
height: 75,
width: 75,
};

export default Logo;
8 changes: 6 additions & 2 deletions src/Components/NodeEnv/index.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Badge } from 'react-native-paper';
import CustomPropTypes from 'Components/CustomPropTypes';

/**
* Display NODE_ENV value by default.
Expand All @@ -12,15 +13,18 @@ import { Badge } from 'react-native-paper';
*/
function NodeEnv({ title, value, ...passThroughProps }) {
return (
<Badge title={title} {...passThroughProps}>
<Badge
title={title}
{...passThroughProps}
>
{value}
</Badge>
);
}

NodeEnv.propTypes = {
title: PropTypes.string,
value: PropTypes.oneOf(['development', 'test', 'production']),
value: CustomPropTypes.nodeEnv,
};

NodeEnv.defaultProps = {
Expand Down
1 change: 1 addition & 0 deletions src/Components/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default as Logo } from './Logo';
export { default as Loader } from './Loader';
export { default as NodeEnv } from './NodeEnv';
22 changes: 21 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -929,7 +929,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.14.5"

"@babel/plugin-transform-object-assign@^7.0.0":
"@babel/plugin-transform-object-assign@^7.0.0", "@babel/plugin-transform-object-assign@^7.10.4":
natsukiai marked this conversation as resolved.
Show resolved Hide resolved
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.14.5.tgz#62537d54b6d85de04f4df48bfdba2eebff17b760"
integrity sha512-lvhjk4UN9xJJYB1mI5KC0/o1D5EcJXdbhVe+4fSk08D6ZN+iuAIs7LJC+71h8av9Ew4+uRq9452v9R93SFmQlQ==
Expand Down Expand Up @@ -8302,6 +8302,11 @@ mkdirp@^1.0.4:
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==

mockdate@^3.0.2:
version "3.0.5"
resolved "https://registry.yarnpkg.com/mockdate/-/mockdate-3.0.5.tgz#789be686deb3149e7df2b663d2bc4392bc3284fb"
integrity sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ==

module-alias@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.2.tgz#151cdcecc24e25739ff0aa6e51e1c5716974c0e0"
Expand Down Expand Up @@ -9799,6 +9804,16 @@ react-native-paper@^4.9.2:
color "^3.1.2"
react-native-iphone-x-helper "^1.3.1"

react-native-reanimated@~2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-2.2.0.tgz#a6412c56b4e591d1f00fac949f62d0c72c357c78"
integrity sha512-lOJDd+5w1gY6DHGXG2jD1dsjzQmXQ2699HUc3IztvI2WP4zUT+UAA+zSG+5JiBS5DUnTL8YhhkmUQmr1KNGO5w==
dependencies:
"@babel/plugin-transform-object-assign" "^7.10.4"
fbjs "^3.0.0"
mockdate "^3.0.2"
string-hash-64 "^1.0.3"

react-native-safe-area-context@3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-3.2.0.tgz#06113c6b208f982d68ab5c3cebd199ca93db6941"
Expand Down Expand Up @@ -10995,6 +11010,11 @@ stream-buffers@~2.2.0:
resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-2.2.0.tgz#91d5f5130d1cef96dcfa7f726945188741d09ee4"
integrity sha1-kdX1Ew0c75bc+n9yaUUYh0HQnuQ=

string-hash-64@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/string-hash-64/-/string-hash-64-1.0.3.tgz#0deb56df58678640db5c479ccbbb597aaa0de322"
integrity sha512-D5OKWKvDhyVWWn2x5Y9b+37NUllks34q1dCDhk/vYcso9fmhs+Tl3KR/gE4v5UNj2UA35cnX4KdVVGkG1deKqw==

string-width@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
Expand Down