Add AnimatedCheckmark

This commit is contained in:
Eric Schultz 2021-03-10 14:47:11 -06:00 committed by Eric Schultz
parent 0162857021
commit 74f2b4e47e
3 changed files with 234 additions and 0 deletions

View file

@ -0,0 +1,40 @@
// License: LGPL-3.0-or-later
import * as React from "react";
import { render, waitFor } from "@testing-library/react";
import '@testing-library/jest-dom/extend-expect';
import AnimatedCheckmark from "./AnimatedCheckmark";
import { IntlProvider } from "../../intl";
import I18n from '../../../i18n';
function Wrapper(props:React.PropsWithChildren<unknown>) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const translations = I18n.translations['en'] as any;
return (<IntlProvider messages={translations} locale={'en'}>
{props.children}
</IntlProvider>);
}
describe('Animated Checkmark', () => {
it('check if it renders', async () => {
expect.hasAssertions();
const {getByTestId} = render(<Wrapper><AnimatedCheckmark ariaLabel={"login.success"} role={"status"}></AnimatedCheckmark></Wrapper>);
const checkmark = getByTestId("CheckmarkTest");
await waitFor(() => {
expect(checkmark).toBeInTheDocument();
});
});
it('check Aria Label Message', async () => {
expect.hasAssertions();
const {queryByLabelText } = render(<Wrapper><AnimatedCheckmark ariaLabel={"You have successfully signed in."} role={"status"}></AnimatedCheckmark></Wrapper>);
await waitFor(() => {
expect(queryByLabelText("You have successfully signed in.")).toBeInTheDocument();
});
});
it('role has status as value', async () => {
expect.hasAssertions();
const {getByTestId } = render(<Wrapper><AnimatedCheckmark ariaLabel={"login.success"} role={"status"}></AnimatedCheckmark></Wrapper>);
const role = getByTestId("CheckmarkTest");
await waitFor(() => {
expect(role).toHaveAttribute('role', 'status');
});
});
});

View file

@ -0,0 +1,25 @@
// License: LGPL-3.0-or-later
import * as React from 'react';
import AnimatedCheckmark, {AnimatedCheckmarkProps} from './AnimatedCheckmark';
export default {
title: 'users/AnimatedCheckmark',
component: AnimatedCheckmark,
};
type TemplateArgs = AnimatedCheckmarkProps;
const CheckmarkTemplate = (args:TemplateArgs) => {
return <AnimatedCheckmark
{...args}
key={Math.random() /* so it reloads everytime props change */}></AnimatedCheckmark>;
};
export const Checkmark = CheckmarkTemplate.bind({});
Checkmark.args = {
};

View file

@ -0,0 +1,169 @@
// License: LGPL-3.0-or-later
// from: https://github.com/davidwilson3/react-typescript-checkmark/blob/1de3e0362965602d4345868f1f876aa54a96d5b6/src/checkmark.tsx
import React from 'react';
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles';
interface StyledProps {
animationDuration: number;
backgroundColor: string;
checkColor: string;
checkThickness: number;
explosion: number;
height: number;
width: number;
}
const useStyles = (makeStyles((theme:Theme) =>
createStyles({
root: {
display: "block",
marginLeft: "auto",
marginRight: "auto",
borderRadius: "50%",
width: (props:StyledProps) => props.width,
height: (props:StyledProps) => props.height,
stroke: (props:StyledProps) => props.checkColor,
strokeWidth: (props:StyledProps) => props.checkThickness,
strokeMiterlimit: 10,
animation: (props:StyledProps) => `$fill ${props.animationDuration * 0.66}s ease-in-out 0.4s
forwards,
$scale 0.3s ease-in-out 0.9s both`,
},
circle: {
strokeDasharray: 166,
strokeDashoffset: 166,
strokeWidth: (props:StyledProps) => props.checkThickness,
strokeMiterlimit: 10,
stroke: (props:StyledProps) => props.backgroundColor || theme.palette.success.main,
fill: "none",
animation: (props:StyledProps) => `$stroke-keyframe ${props.animationDuration}s
cubic-bezier(0.65, 0, 0.45, 1) forwards`,
},
checkmark: {
transformOrigin: "50% 50%",
strokeDasharray: 48,
strokeDashoffset: 48,
animation: (props:StyledProps) => `$stroke-keyframe ${props.animationDuration * 0.5}s
cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards`,
},
"@keyframes scale": {
"0%": {},
"100%": {
transform: "none",
},
"50%": {
transform: (props:StyledProps) => `scale3d(${props.explosion}, ${props.explosion}, 1)`,
},
},
"@keyframes fill": {
"100%": {
boxShadow: (props:StyledProps) => `inset 0 0 0 100vh ${props.backgroundColor || theme.palette.success.main}`,
},
},
"@keyframes stroke-keyframe": {
"100%": {
/* this is needed because makeStyles function has bugs (https://github.com/mui-org/material-ui/issues/15511) */
strokeDashoffset:() => 0,
},
},
})
));
/**
* The different preset checkmark sizes. Measured in pixels
*/
export const sizes = {
xs: 12,
sm: 16,
md: 24,
lg: 52,
xl: 72,
xxl: 96,
};
export type Sizes = keyof typeof sizes;
export interface AnimatedCheckmarkProps {
/**
* Duration of the checkmark animation in milliseconds
*/
animationDuration: number;
/** A string for describing what the component means in screen readers*/
ariaLabel: string;
/** Color in hex of the circle and background of component
* Defaults to `theme.palette.success.main`
*/
backgroundColor?: string;
/**
* Color in hex of checkmark in the middle of the component
* Defaults to white (#000)
*/
checkColor?: string;
/**
* The stroke width of the checkmark. Defaults to 5.
*/
checkThickness: number;
/**
* How much the circle temporarily expands to on success. 1 means no expansion, 1.1 means 10% expansion, etc.
* Defaults to 1.1
*/
explosion: number;
/**
* The role for accessibility purposes.
* Defaults to 'alert'.
*/
role?: string;
/**
* The height and width in pixels of the component. Accepts a size string (listed in `sizes`) or a number
*
* Defaults to 'lg' which is 52 pixels
*/
size: Sizes | number;
/**
* Whether the component should be visible in the document.
*/
visible: boolean;
}
function AnimatedCheckmark(props: AnimatedCheckmarkProps): JSX.Element {
const selectedSize = typeof props.size === 'number' ? props.size : sizes[props.size];
const classes = useStyles({
backgroundColor: props.backgroundColor,
checkColor: props.checkColor,
checkThickness: props.checkThickness,
animationDuration: props.animationDuration,
explosion: props.explosion,
width: selectedSize,
height: selectedSize,
});
if (!props.visible) return <></>;
return (
<svg
data-testid="CheckmarkTest"
className={classes.root}
xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 52 52' role={props.role} aria-label={props.ariaLabel}
>
<circle className={classes.circle} cx='26' cy='26' r='25' fill='none' />
<path className={classes.checkmark} fill='none' d='M14.1 27.2l7.1 7.2 16.7-16.8' />
</svg>
);
}
AnimatedCheckmark.defaultProps = {
size: 'lg',
visible: true,
checkColor: '#FFF',
checkThickness: 5,
animationDuration: 0.6,
explosion: 1.1,
role: 'alert',
};
export default AnimatedCheckmark;