Skip to content

Commit

Permalink
Merge pull request #49 from MetroStar/CSG-718
Browse files Browse the repository at this point in the history
CSG-718: Add Custom Spinner component to Comet Extras
  • Loading branch information
jbouder committed Jun 7, 2023
2 parents ffaf79a + 0fe9592 commit 06abc3d
Show file tree
Hide file tree
Showing 6 changed files with 272 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/comet-extras/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default } from './spinner';
export { default as Tabs, TabPanel } from './tabs';
1 change: 1 addition & 0 deletions packages/comet-extras/src/components/spinner/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './spinner';
23 changes: 23 additions & 0 deletions packages/comet-extras/src/components/spinner/spinner.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import { Meta, StoryFn } from '@storybook/react';
import Spinner, { SpinnerProps } from './spinner';

const meta: Meta<typeof Spinner> = {
title: 'Extras/Spinner',
component: Spinner,
argTypes: {
id: { required: true },
type: { control: 'select' },
},
};
export default meta;

const Template: StoryFn<typeof Spinner> = (args: SpinnerProps) => <Spinner {...args} />;

export const Default = Template.bind({});
Default.args = {
id: 'spinner',
type: 'large',
loadingText: 'Loading...',
className: '',
};
173 changes: 173 additions & 0 deletions packages/comet-extras/src/components/spinner/spinner.style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
.spinner-container-large {
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
width: 75px;
}

.spinner-container-small {
display: flex;
flex-direction: row;
width: 105px;
}

.spinner-large {
width: 100%;
justify-content: center;
text-align: center;
position: relative;
left: 25px;
padding-bottom: 30px;
}

.spinner-small {
width: 100%;
position: relative;
left: 10px;
}

.dot-spinner-large {
position: relative;
width: 10px;
height: 10px;
border-radius: 5px;
background-color: transparent;
color: transparent;
box-shadow: 0 -12px 0 0 #0076d6, 12.727926px -12.727926px 0 0 #0076d6, 18px 0 0 0 #0076d6,
12.727926px 12.727926px 0 0 rgba(152, 128, 255, 0), 0 18px 0 0 rgba(152, 128, 255, 0),
-12.727926px 12.727926px 0 0 rgba(152, 128, 255, 0), -18px 0 0 0 rgba(152, 128, 255, 0),
-12.727926px -12.727926px 0 0 rgba(152, 128, 255, 0);
animation: dot-spin 1.5s infinite linear;
}

.dot-spinner-small {
position: relative;
width: 5px;
height: 5px;
border-radius: 5px;
background-color: transparent;
color: transparent;
box-shadow: 0 -6px 0 0 #0076d6, 6.727926px -6.727926px 0 0 #0076d6, 9px 0 0 0 #0076d6,
6.727926px 6.727926px 0 0 rgba(152, 128, 255, 0), 0 9px 0 0 rgba(152, 128, 255, 0),
-6.727926px 6.727926px 0 0 rgba(152, 128, 255, 0), -9px 0 0 0 rgba(152, 128, 255, 0),
-6.727926px -6.727926px 0 0 rgba(152, 128, 255, 0);
animation: dot-spin-small 1.5s infinite linear;
}

.loading-text {
width: 100%;
font-weight: 600;
color: #0076d6;
position: relative;
bottom: 5px;
}

@keyframes dot-spin-small {
0%,
100% {
box-shadow: 0 -9px 0 0 #0076d6, 6.727926px -6.727926px 0 0 #0076d6, 9px 0 0 0 #0076d6,
6.727926px 6.727926px 0 -5px rgba(152, 128, 255, 0), 0 9px 0 -5px rgba(152, 128, 255, 0),
-6.727926px 6.727926px 0 -5px rgba(152, 128, 255, 0), -9px 0 0 -5px rgba(152, 128, 255, 0),
-6.727926px -6.727926px 0 -5px rgba(152, 128, 255, 0);
}
6.5% {
box-shadow: 0 -9px 0 -5px rgba(152, 128, 255, 0), 6.727926px -6.727926px 0 0 #0076d6,
9px 0 0 0 #0076d6, 6.727926px 6.727926px 0 0 #0076d6, 0 9px 0 -5px rgba(152, 128, 255, 0),
-6.727926px 6.727926px 0 -5px rgba(152, 128, 255, 0), -9px 0 0 -5px rgba(152, 128, 255, 0),
-6.727926px -6.727926px 0 -5px rgba(152, 128, 255, 0);
}
25% {
box-shadow: 0 -9px 0 -5px rgba(152, 128, 255, 0),
6.727926px -6.727926px 0 -5px rgba(152, 128, 255, 0), 9px 0 0 0 #0076d6,
6.727926px 6.727926px 0 0 #0076d6, 0 9px 0 0 #0076d6,
-6.727926px 6.727926px 0 -5px rgba(152, 128, 255, 0), -9px 0 0 -5px rgba(152, 128, 255, 0),
-6.727926px -6.727926px 0 -5px rgba(152, 128, 255, 0);
}
37.5% {
box-shadow: 0 -9px 0 -5px rgba(152, 128, 255, 0),
6.727926px -6.727926px 0 -5px rgba(152, 128, 255, 0), 9px 0 0 -5px rgba(152, 128, 255, 0),
6.727926px 6.727926px 0 0 #0076d6, 0 9px 0 0 #0076d6, -6.727926px 6.727926px 0 0 #0076d6,
-9px 0 0 -5px rgba(152, 128, 255, 0), -6.727926px -6.727926px 0 -5px rgba(152, 128, 255, 0);
}
50% {
box-shadow: 0 -9px 0 -5px rgba(152, 128, 255, 0),
6.727926px -6.727926px 0 -5px rgba(152, 128, 255, 0), 9px 0 0 -5px rgba(152, 128, 255, 0),
6.727926px 6.727926px 0 -5px rgba(152, 128, 255, 0), 0 9px 0 0 #0076d6,
-6.727926px 6.727926px 0 0 #0076d6, -9px 0 0 0 #0076d6,
-6.727926px -6.727926px 0 -5px rgba(152, 128, 255, 0);
}
62.5% {
box-shadow: 0 -9px 0 -5px rgba(152, 128, 255, 0),
6.727926px -6.727926px 0 -5px rgba(152, 128, 255, 0), 9px 0 0 -5px rgba(152, 128, 255, 0),
6.727926px 6.727926px 0 -5px rgba(152, 128, 255, 0), 0 9px 0 -5px rgba(152, 128, 255, 0),
-6.727926px 6.727926px 0 0 #0076d6, -9px 0 0 0 #0076d6, -6.727926px -6.727926px 0 0 #0076d6;
}
75% {
box-shadow: 0 -9px 0 0 #0076d6, 6.727926px -6.727926px 0 -5px rgba(152, 128, 255, 0),
9px 0 0 -5px rgba(152, 128, 255, 0), 6.727926px 6.727926px 0 -5px rgba(152, 128, 255, 0),
0 9px 0 -5px rgba(152, 128, 255, 0), -6.727926px 6.727926px 0 -5px rgba(152, 128, 255, 0),
-9px 0 0 0 #0076d6, -6.727926px -6.727926px 0 0 #0076d6;
}
87.5% {
box-shadow: 0 -9px 0 0 #0076d6, 6.727926px -6.727926px 0 0 #0076d6,
9px 0 0 -5px rgba(152, 128, 255, 0), 6.727926px 6.727926px 0 -5px rgba(152, 128, 255, 0),
0 9px 0 -5px rgba(152, 128, 255, 0), -6.727926px 6.727926px 0 -5px rgba(152, 128, 255, 0),
-9px 0 0 -5px rgba(152, 128, 255, 0), -6.727926px -6.727926px 0 0 #0076d6;
}
}

@keyframes dot-spin {
0%,
100% {
box-shadow: 0 -18px 0 0 #0076d6, 12.727926px -12.727926px 0 0 #0076d6, 18px 0 0 0 #0076d6,
12.727926px 12.727926px 0 -5px rgba(152, 128, 255, 0), 0 18px 0 -5px rgba(152, 128, 255, 0),
-12.727926px 12.727926px 0 -5px rgba(152, 128, 255, 0), -18px 0 0 -5px rgba(152, 128, 255, 0),
-12.727926px -12.727926px 0 -5px rgba(152, 128, 255, 0);
}
12.5% {
box-shadow: 0 -18px 0 -5px rgba(152, 128, 255, 0), 12.727926px -12.727926px 0 0 #0076d6,
18px 0 0 0 #0076d6, 12.727926px 12.727926px 0 0 #0076d6, 0 18px 0 -5px rgba(152, 128, 255, 0),
-12.727926px 12.727926px 0 -5px rgba(152, 128, 255, 0), -18px 0 0 -5px rgba(152, 128, 255, 0),
-12.727926px -12.727926px 0 -5px rgba(152, 128, 255, 0);
}
25% {
box-shadow: 0 -18px 0 -5px rgba(152, 128, 255, 0),
12.727926px -12.727926px 0 -5px rgba(152, 128, 255, 0), 18px 0 0 0 #0076d6,
12.727926px 12.727926px 0 0 #0076d6, 0 18px 0 0 #0076d6,
-12.727926px 12.727926px 0 -5px rgba(152, 128, 255, 0), -18px 0 0 -5px rgba(152, 128, 255, 0),
-12.727926px -12.727926px 0 -5px rgba(152, 128, 255, 0);
}
37.5% {
box-shadow: 0 -18px 0 -5px rgba(152, 128, 255, 0),
12.727926px -12.727926px 0 -5px rgba(152, 128, 255, 0), 18px 0 0 -5px rgba(152, 128, 255, 0),
12.727926px 12.727926px 0 0 #0076d6, 0 18px 0 0 #0076d6, -12.727926px 12.727926px 0 0 #0076d6,
-18px 0 0 -5px rgba(152, 128, 255, 0), -12.727926px -12.727926px 0 -5px rgba(152, 128, 255, 0);
}
50% {
box-shadow: 0 -18px 0 -5px rgba(152, 128, 255, 0),
12.727926px -12.727926px 0 -5px rgba(152, 128, 255, 0), 18px 0 0 -5px rgba(152, 128, 255, 0),
12.727926px 12.727926px 0 -5px rgba(152, 128, 255, 0), 0 18px 0 0 #0076d6,
-12.727926px 12.727926px 0 0 #0076d6, -18px 0 0 0 #0076d6,
-12.727926px -12.727926px 0 -5px rgba(152, 128, 255, 0);
}
62.5% {
box-shadow: 0 -18px 0 -5px rgba(152, 128, 255, 0),
12.727926px -12.727926px 0 -5px rgba(152, 128, 255, 0), 18px 0 0 -5px rgba(152, 128, 255, 0),
12.727926px 12.727926px 0 -5px rgba(152, 128, 255, 0), 0 18px 0 -5px rgba(152, 128, 255, 0),
-12.727926px 12.727926px 0 0 #0076d6, -18px 0 0 0 #0076d6,
-12.727926px -12.727926px 0 0 #0076d6;
}
75% {
box-shadow: 0 -18px 0 0 #0076d6, 12.727926px -12.727926px 0 -5px rgba(152, 128, 255, 0),
18px 0 0 -5px rgba(152, 128, 255, 0), 12.727926px 12.727926px 0 -5px rgba(152, 128, 255, 0),
0 18px 0 -5px rgba(152, 128, 255, 0), -12.727926px 12.727926px 0 -5px rgba(152, 128, 255, 0),
-18px 0 0 0 #0076d6, -12.727926px -12.727926px 0 0 #0076d6;
}
87.5% {
box-shadow: 0 -18px 0 0 #0076d6, 12.727926px -12.727926px 0 0 #0076d6,
18px 0 0 -5px rgba(152, 128, 255, 0), 12.727926px 12.727926px 0 -5px rgba(152, 128, 255, 0),
0 18px 0 -5px rgba(152, 128, 255, 0), -12.727926px 12.727926px 0 -5px rgba(152, 128, 255, 0),
-18px 0 0 -5px rgba(152, 128, 255, 0), -12.727926px -12.727926px 0 0 #0076d6;
}
}
24 changes: 24 additions & 0 deletions packages/comet-extras/src/components/spinner/spinner.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';
import '@testing-library/jest-dom';
import { render } from '@testing-library/react';
import Spinner from './spinner';

describe('Spinner', () => {
test('should render a default spinner', () => {
const { container } = render(<Spinner id="spinner" />);
expect(container.querySelector('#spinner')).toBeTruthy();
expect(container.querySelector('#spinner > div')).toHaveClass('spinner-large');
});

test('should render a small spinner', () => {
const { container } = render(<Spinner id="spinner" type="small" />);
expect(container.querySelector('#spinner')).toBeTruthy();
expect(container.querySelector('#spinner > div')).toHaveClass('spinner-small');
});

test('should render a spinner with loading text', () => {
const { container } = render(<Spinner id="spinner" loadingText="Loading..." />);
expect(container.querySelector('#spinner')).toBeTruthy();
expect(container.querySelector('.loading-text')).toBeTruthy();
});
});
50 changes: 50 additions & 0 deletions packages/comet-extras/src/components/spinner/spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react';
import classnames from 'classnames';
import './spinner.style.css';

export interface SpinnerProps {
/**
* The unique identifier for this component
*/
id: string;
/**
* The size of the spinner to display
*/
type?: 'small' | 'large';
/**
* An optional text value to display with the indicator
*/
loadingText?: string;
/**
* A custom class to apply to the component
*/
className?: string;
}

/**
* Used to provide the loading state of some action.
*/
export const Spinner = ({
id,
type = 'large',
loadingText = 'Loading...',
className,
...props
}: SpinnerProps & JSX.IntrinsicElements['div']): React.ReactElement => {
const classes = classnames(
{ 'spinner-container-large': type === 'large' },
{ 'spinner-container-small': type === 'small' },
className,
);

return (
<div id={id} className={classes} {...props}>
<div className={type === 'large' ? 'spinner-large' : 'spinner-small'}>
<div className={type === 'large' ? 'dot-spinner-large' : 'dot-spinner-small'}></div>
</div>
{loadingText && <div className="loading-text">{loadingText}</div>}
</div>
);
};

export default Spinner;

0 comments on commit 06abc3d

Please sign in to comment.