Add AnimatedCheckmark
This commit is contained in:
parent
0162857021
commit
74f2b4e47e
3 changed files with 234 additions and 0 deletions
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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 = {
|
||||
};
|
||||
|
||||
|
||||
|
169
app/javascript/components/common/progress/AnimatedCheckmark.tsx
Normal file
169
app/javascript/components/common/progress/AnimatedCheckmark.tsx
Normal 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;
|
Loading…
Reference in a new issue