Skip to content

Commit

Permalink
feat(LoadingIndicator)!: introduce 2.0 component (#1963)
Browse files Browse the repository at this point in the history
- add tests and snapshots
- update API and component documentation
- update token usages
  • Loading branch information
booc0mtaco authored May 24, 2024
1 parent 675ad5f commit 26faab7
Show file tree
Hide file tree
Showing 7 changed files with 577 additions and 0 deletions.
34 changes: 34 additions & 0 deletions src/components/LoadingIndicator/LoadingIndicator-v2.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*------------------------------------*\
# LOADINGINDICATOR
\*------------------------------------*/

/**
* LoadingIndicator
*/
.loading-indicator {
background-color: transparent;
padding: 3px; /* ported from react-loader-spinner */
display: flex;
}

.loading-indicator--invisible {
display: none;
}

/* override for `color` */
.loading-indicator > svg path {
stroke: var(--eds-theme-color-border-utility-informational);
}

/* override for `secondaryColor` */
.loading-indicator > svg circle {
stroke: var(--eds-theme-color-border-utility-default-low-emphasis);
stroke-opacity: 1;
}

@media screen and (prefers-reduced-motion: reduce) {
/* if reducing motion, don't show the moving portion */
.loading-indicator > svg path {
stroke: none;
}
}
53 changes: 53 additions & 0 deletions src/components/LoadingIndicator/LoadingIndicator-v2.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { StoryObj, Meta } from '@storybook/react';
import type React from 'react';

import { LoadingIndicator } from './LoadingIndicator-v2';

export default {
title: 'Components/V2/LoadingIndicator',
component: LoadingIndicator,
parameters: {
layout: 'centered',
badges: ['intro-1.2', 'current-2.0'],
},
} as Meta<Args>;

type Args = React.ComponentProps<typeof LoadingIndicator>;

export const Default: StoryObj<Args> = {};

export const ExtraSmall: StoryObj<Args> = {
args: {
size: 'xs',
},
};

export const Small: StoryObj<Args> = {
args: {
size: 'sm',
},
};

export const Medium: StoryObj<Args> = {
args: {
size: 'md',
},
};

export const Large: StoryObj<Args> = {
args: {
size: 'lg',
},
};

export const Invisible: StoryObj<Args> = {
args: {
isVisible: false,
},
};

export const WithAriaLabel: StoryObj<Args> = {
args: {
ariaLabel: 'Loading, Please Wait',
},
};
6 changes: 6 additions & 0 deletions src/components/LoadingIndicator/LoadingIndicator-v2.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { generateSnapshots } from '@chanzuckerberg/story-utils';
import * as stories from './LoadingIndicator-v2.stories';

describe('<LoadingIndicator /> (v2)', () => {
generateSnapshots(stories);
});
124 changes: 124 additions & 0 deletions src/components/LoadingIndicator/LoadingIndicator-v2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import clsx from 'clsx';
import React from 'react';

import type { Size } from '../../util/variant-types';

import styles from './LoadingIndicator-v2.module.css';

export type LoadingIndicatorProps = {
// Component API
/**
* CSS class names that can be appended to the component.
*/
className?: string;
/**
* Aria label of the oval. Default is "loading". Will be overridden if ariaLabel is passed in props
*/
ariaLabel?: string;
// Design API
/**
* Layout size of the loader. This affects the overall size and associated
* stroke width.
*/
size?: Extract<Size, 'xs' | 'sm' | 'md' | 'lg'>;
/**
* Whether the oval is visible. Default is true.
*/
isVisible?: boolean;
};

// Pixel sizes corresponding to EDS size units
const loaderSize = {
xs: 16, // --eds-size-1
sm: 24, // --eds-size-2-and-half
md: 40, // --eds-size-5
lg: 56, // --eds-size-7
};

// Given a loader size, the stroke widths can change
const loaderStrokeSize = {
xs: 2,
sm: 2,
md: 3,
lg: 4,
};

// The viewport changes based on adjustments to handle the stroke width
const loaderViewportSize = {
xs: '-20 -20 42 42',
sm: '-20 -20 42 42',
md: '-20.5 -20.5 43 43',
lg: '-21 -21 44 44',
};

/**
* `import {LoadingIndicator} from "@chanzuckerberg/eds";`
*
* Loading indicators inform users about the wait time, reason, and status of ongoing processes when the layout is unknown
*
* For screen readers, add a custom `aria-label` to describe what is loading.
*/
export const LoadingIndicator = ({
ariaLabel = 'loading',
className,
size = 'md',
isVisible: visible = true,
...other
}: LoadingIndicatorProps) => {
const componentClassName = clsx(
styles['loading-indicator'],
!visible && styles['loading-indicator--invisible'],
className,
);

// setting the colors to be transparent since we override in CSS
// (and have a lint rule prevent token variable use in components)
return (
<div
aria-busy="true"
aria-label={ariaLabel}
className={componentClassName}
data-testid="oval-loading"
role="status"
{...other}
>
<svg
data-testid="oval-svg"
height={loaderSize[size]}
stroke="transparent"
viewBox={loaderViewportSize[size]}
width={loaderSize[size]}
xmlns="http://www.w3.org/2000/svg"
>
<g fill="none" fillRule="evenodd">
<g
data-testid="oval-secondary-group"
strokeWidth={loaderStrokeSize[size]}
transform="translate(1 1)"
>
<circle
cx="0"
cy="0"
r="20"
stroke="transparent"
strokeOpacity=".5"
strokeWidth={loaderStrokeSize[size]}
></circle>
<path d="M20 0c0-9.94-8.06-20-20-20">
<animateTransform
attributeName="transform"
dur="1s"
from="0 0 0"
repeatCount="indefinite"
to="360 0 0"
type="rotate"
></animateTransform>
</path>
</g>
</g>
</svg>
</div>
);
};

LoadingIndicator.displayName = 'LoadingIndicator';
Loading

0 comments on commit 26faab7

Please sign in to comment.