From 5d0d027566bf169a18f8ceda4791cfce5a840ab4 Mon Sep 17 00:00:00 2001 From: Haosheng Li Date: Wed, 28 Jul 2021 10:23:08 -0500 Subject: [PATCH 1/4] add ripple loader component from trendkite-ui --- src/components/Loader/Loader.module.css | 80 +++++++++++++++++++++++++ src/components/Loader/Loader.test.tsx | 40 +++++++++++++ src/components/Loader/Loader.tsx | 33 ++++++++++ src/components/Loader/README.md | 16 +++++ src/components/Loader/index.ts | 1 + src/components/Loader/story.tsx | 69 +++++++++++++++++++++ src/stories/index.js | 1 + 7 files changed, 240 insertions(+) create mode 100644 src/components/Loader/Loader.module.css create mode 100644 src/components/Loader/Loader.test.tsx create mode 100644 src/components/Loader/Loader.tsx create mode 100644 src/components/Loader/README.md create mode 100644 src/components/Loader/index.ts create mode 100644 src/components/Loader/story.tsx diff --git a/src/components/Loader/Loader.module.css b/src/components/Loader/Loader.module.css new file mode 100644 index 00000000..0e9ad6c8 --- /dev/null +++ b/src/components/Loader/Loader.module.css @@ -0,0 +1,80 @@ +@keyframes LOADERPUFF { + 0% { + transform: scale(0, 0); + opacity: 1; + } + + 100% { + transform: scale(0.9, 0.9); + opacity: 0; + } +} + +.loader { + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: center; +} + +.loader-icon { + background-color: transparent; + position: relative; + z-index: 100; + width: 50px; + height: 50px; +} + +.loader-icon::before { + content: ''; + background-color: transparent; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 1; + border-radius: 50%; + border: 5px solid #0092c2; + animation: LOADERPUFF 1.8s infinite; + transform: scale(0, 0); + animation-timing-function: cubic-bezier(0.165, 0.84, 0.44, 1); + animation-delay: 0s; +} + +.loader-icon::after { + content: ''; + background-color: transparent; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 1; + border-radius: 50%; + border: 5px solid #0092c2; + animation: LOADERPUFF 1.8s infinite; + transform: scale(0, 0); + animation-timing-function: cubic-bezier(0.3, 0.61, 0.335, 1); + animation-delay: -0.9s; +} + +.size--sm { + width: 10px; + height: 10px; +} + +.size--md { + width: 20px; + height: 20px; +} + +.size--lg { + width: 50px; + height: 50px; +} + +.size--xl { + width: 100px; + height: 100px; +} diff --git a/src/components/Loader/Loader.test.tsx b/src/components/Loader/Loader.test.tsx new file mode 100644 index 00000000..1449bd79 --- /dev/null +++ b/src/components/Loader/Loader.test.tsx @@ -0,0 +1,40 @@ +import React from 'react'; + +import { render } from '@testing-library/react'; +import '@testing-library/jest-dom'; + +import Loader from '.'; + +describe('Loader', () => { + it('renders', () => { + render(); + }); + + describe('when rendered with custom style with width and height', () => { + it('should override the default loader sizes', () => { + const { getByTestId } = render( + + ); + + const loaderElem = getByTestId('loaderElem') as HTMLDivElement; + expect(loaderElem.style.width).toBe('123px'); + }); + }); + + describe('when rendered with additional class names', () => { + it('should not replace the default classes', () => { + const { getByTestId } = render( + + ); + + const loaderElem = getByTestId('loaderElem') as HTMLDivElement; + const loaderClasses = loaderElem.className + .split(' ') + .filter((classes) => classes.match('\\w*(loader-icon)\\w*')); + expect(loaderClasses.length).toBe(2); + }); + }); +}); diff --git a/src/components/Loader/Loader.tsx b/src/components/Loader/Loader.tsx new file mode 100644 index 00000000..22f27955 --- /dev/null +++ b/src/components/Loader/Loader.tsx @@ -0,0 +1,33 @@ +import React from 'react'; + +import classNames from 'classnames'; + +import styles from './Loader.module.css'; + +export type Size = 'sm' | 'md' | 'lg' | 'xl'; + +interface LoaderProps extends React.HTMLAttributes { + size?: Size; +} + +const Loader: React.FC = ({ size = 'lg', ...rest }) => { + const mainClass = classNames(styles.loader); + const iconClass = classNames( + styles['loader-icon'], + styles[`size--${size}`], + rest.className + ); + + const iconProp = { + ...rest, + className: iconClass, + }; + + return ( +
+
+
+ ); +}; + +export default Loader; diff --git a/src/components/Loader/README.md b/src/components/Loader/README.md new file mode 100644 index 00000000..79da27ec --- /dev/null +++ b/src/components/Loader/README.md @@ -0,0 +1,16 @@ +# \ + +render a loading icon + +### Valid Sizes + +Size is optional, defaults to large when omitted + +- sm +- md +- lg +- xl + +```jsx + +``` diff --git a/src/components/Loader/index.ts b/src/components/Loader/index.ts new file mode 100644 index 00000000..348c02a9 --- /dev/null +++ b/src/components/Loader/index.ts @@ -0,0 +1 @@ +export { default } from './Loader'; diff --git a/src/components/Loader/story.tsx b/src/components/Loader/story.tsx new file mode 100644 index 00000000..28c1db04 --- /dev/null +++ b/src/components/Loader/story.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { storiesOf } from '@storybook/react'; + +import Loader from './Loader'; +import Readme from './README.md'; + +import { Wrap } from '../../stories/storybook-helpers'; + +const FlexWrapper = (props) => ( +
+); + +storiesOf('Planets/Loader', module) + .addParameters({ + readme: { + sidebar: Readme, + }, + }) + .add('Overview', () => ( + + + + + )) + .add( + 'Examples', + () => ( + <> + + + + + + + + + + + + + + + + + + + + + + + ), + { + info: { + inline: false, + source: true, + }, + } + ); diff --git a/src/stories/index.js b/src/stories/index.js index 6124ceb7..21259e0d 100644 --- a/src/stories/index.js +++ b/src/stories/index.js @@ -24,6 +24,7 @@ import '../components/Input/story'; import '../components/Input/Checkbox/story'; import '../components/InputTime/story'; import '../components/Typography/story'; +import '../components/Loader/story'; /* * STAR SYSTEMS From 520ffb3234c8d86adfc6ec36bc0af344de708021 Mon Sep 17 00:00:00 2001 From: Haosheng Li Date: Fri, 30 Jul 2021 14:07:07 -0500 Subject: [PATCH 2/4] update ripple loader to render without div nesting --- example/src/App.js | 9 +++++ src/components/Loader/Loader.module.css | 50 ++++++++++++------------- src/components/Loader/Loader.test.tsx | 4 +- src/components/Loader/Loader.tsx | 19 ++++------ src/components/Loader/story.tsx | 9 ++++- src/index.ts | 1 + 6 files changed, 51 insertions(+), 41 deletions(-) diff --git a/example/src/App.js b/example/src/App.js index d22c75d7..f5d08254 100644 --- a/example/src/App.js +++ b/example/src/App.js @@ -28,6 +28,7 @@ import { Typography, Modal, Kite, + Loader, // IMPORT_INJECTOR } from '@cision/rover-ui'; @@ -474,6 +475,14 @@ const App = () => { Success Kite! +
+
+ +
+
+ +
+
{/** USAGE_INJECTOR */}
diff --git a/src/components/Loader/Loader.module.css b/src/components/Loader/Loader.module.css index 0e9ad6c8..91209344 100644 --- a/src/components/Loader/Loader.module.css +++ b/src/components/Loader/Loader.module.css @@ -1,4 +1,4 @@ -@keyframes LOADERPUFF { +@keyframes LOADERRIPPLE { 0% { transform: scale(0, 0); opacity: 1; @@ -17,64 +17,64 @@ justify-content: center; } -.loader-icon { - background-color: transparent; - position: relative; - z-index: 100; - width: 50px; - height: 50px; -} - -.loader-icon::before { +.loader::before { content: ''; background-color: transparent; position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; opacity: 1; + width: 50px; + height: 50px; border-radius: 50%; border: 5px solid #0092c2; - animation: LOADERPUFF 1.8s infinite; + animation: LOADERRIPPLE 1.8s infinite; transform: scale(0, 0); animation-timing-function: cubic-bezier(0.165, 0.84, 0.44, 1); animation-delay: 0s; } -.loader-icon::after { +.loader::after { content: ''; background-color: transparent; position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; opacity: 1; + width: 50px; + height: 50px; border-radius: 50%; border: 5px solid #0092c2; - animation: LOADERPUFF 1.8s infinite; + animation: LOADERRIPPLE 1.8s infinite; transform: scale(0, 0); animation-timing-function: cubic-bezier(0.3, 0.61, 0.335, 1); animation-delay: -0.9s; } -.size--sm { +.sm, +.sm::before, +.sm::after { width: 10px; height: 10px; + margin: auto; } -.size--md { +.md, +.md::before, +.md::after { width: 20px; height: 20px; + margin: auto; } -.size--lg { +.lg, +.lg::before, +.lg::after { width: 50px; height: 50px; + margin: auto; } -.size--xl { +.xl, +.xl::before, +.xl::after { width: 100px; height: 100px; + margin: auto; } diff --git a/src/components/Loader/Loader.test.tsx b/src/components/Loader/Loader.test.tsx index 1449bd79..85122730 100644 --- a/src/components/Loader/Loader.test.tsx +++ b/src/components/Loader/Loader.test.tsx @@ -27,13 +27,13 @@ describe('Loader', () => { describe('when rendered with additional class names', () => { it('should not replace the default classes', () => { const { getByTestId } = render( - + ); const loaderElem = getByTestId('loaderElem') as HTMLDivElement; const loaderClasses = loaderElem.className .split(' ') - .filter((classes) => classes.match('\\w*(loader-icon)\\w*')); + .filter((classes) => classes.match('\\w*(loader)\\w*')); expect(loaderClasses.length).toBe(2); }); }); diff --git a/src/components/Loader/Loader.tsx b/src/components/Loader/Loader.tsx index 22f27955..e388e83d 100644 --- a/src/components/Loader/Loader.tsx +++ b/src/components/Loader/Loader.tsx @@ -11,23 +11,18 @@ interface LoaderProps extends React.HTMLAttributes { } const Loader: React.FC = ({ size = 'lg', ...rest }) => { - const mainClass = classNames(styles.loader); - const iconClass = classNames( - styles['loader-icon'], - styles[`size--${size}`], - rest.className + const mainClass = classNames( + styles.loader, + rest.className, + `${styles[size]}` ); - const iconProp = { + const prop = { ...rest, - className: iconClass, + className: mainClass, }; - return ( -
-
-
- ); + return
; }; export default Loader; diff --git a/src/components/Loader/story.tsx b/src/components/Loader/story.tsx index 28c1db04..b4bf0dfc 100644 --- a/src/components/Loader/story.tsx +++ b/src/components/Loader/story.tsx @@ -50,10 +50,15 @@ storiesOf('Planets/Loader', module) - + - + + + +
+ +
diff --git a/src/index.ts b/src/index.ts index 1dfeb6ea..4d71b292 100644 --- a/src/index.ts +++ b/src/index.ts @@ -36,3 +36,4 @@ export { default as InputTime } from './components/InputTime'; export { default as Typography } from './components/Typography'; export { default as Modal } from './components/Modal'; export { default as Kite } from './components/Kite'; +export { default as Loader } from './components/Loader'; From c847702199d67b4d6afe5c9cdecc76ffc332b33a Mon Sep 17 00:00:00 2001 From: Haosheng Li Date: Mon, 2 Aug 2021 14:04:51 -0500 Subject: [PATCH 3/4] add storybook knobs in loader overview example --- src/components/Loader/story.tsx | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/components/Loader/story.tsx b/src/components/Loader/story.tsx index b4bf0dfc..4742eeab 100644 --- a/src/components/Loader/story.tsx +++ b/src/components/Loader/story.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; +import { select } from '@storybook/addon-knobs'; import Loader from './Loader'; import Readme from './README.md'; @@ -27,12 +28,20 @@ storiesOf('Planets/Loader', module) sidebar: Readme, }, }) - .add('Overview', () => ( - - - - - )) + .add( + 'Overview', + () => ( + + + + ), + { + info: { + inline: false, + source: true, + }, + } + ) .add( 'Examples', () => ( From 9efd607d742fd3bf1c52a1c36a173c79c270d3ee Mon Sep 17 00:00:00 2001 From: Haosheng Li Date: Mon, 9 Aug 2021 13:04:21 -0500 Subject: [PATCH 4/4] refactor loader class name --- src/components/Loader/Loader.module.css | 6 +++--- src/components/Loader/Loader.test.tsx | 4 ++-- src/components/Loader/Loader.tsx | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/Loader/Loader.module.css b/src/components/Loader/Loader.module.css index 91209344..a423a407 100644 --- a/src/components/Loader/Loader.module.css +++ b/src/components/Loader/Loader.module.css @@ -10,14 +10,14 @@ } } -.loader { +.Loader { display: flex; flex-flow: row nowrap; align-items: center; justify-content: center; } -.loader::before { +.Loader::before { content: ''; background-color: transparent; position: absolute; @@ -32,7 +32,7 @@ animation-delay: 0s; } -.loader::after { +.Loader::after { content: ''; background-color: transparent; position: absolute; diff --git a/src/components/Loader/Loader.test.tsx b/src/components/Loader/Loader.test.tsx index 85122730..26d90087 100644 --- a/src/components/Loader/Loader.test.tsx +++ b/src/components/Loader/Loader.test.tsx @@ -27,13 +27,13 @@ describe('Loader', () => { describe('when rendered with additional class names', () => { it('should not replace the default classes', () => { const { getByTestId } = render( - + ); const loaderElem = getByTestId('loaderElem') as HTMLDivElement; const loaderClasses = loaderElem.className .split(' ') - .filter((classes) => classes.match('\\w*(loader)\\w*')); + .filter((classes) => classes.match('\\w*(Loader)\\w*')); expect(loaderClasses.length).toBe(2); }); }); diff --git a/src/components/Loader/Loader.tsx b/src/components/Loader/Loader.tsx index e388e83d..3831f50a 100644 --- a/src/components/Loader/Loader.tsx +++ b/src/components/Loader/Loader.tsx @@ -12,7 +12,7 @@ interface LoaderProps extends React.HTMLAttributes { const Loader: React.FC = ({ size = 'lg', ...rest }) => { const mainClass = classNames( - styles.loader, + styles.Loader, rest.className, `${styles[size]}` );