Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 5 additions & 10 deletions src/components/presentational/ProgressRing/ProgressRing.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,22 @@
.mdhui-progress-ring {
position: relative;
margin: 0 auto 0 auto;
width: 220px;
height: 220px;
--mdhui-progress-ring-animation-start: 630;
--mdhui-progress-ring-animation-end: 0;
}

.mdhui-progress-ring svg {
width: 220px;
height: 220px;
.mdhui-progress-ring > svg {
position: relative;
transform: rotate(-90deg);
z-index: 1;
}

.mdhui-progress-ring circle {
.mdhui-progress-ring > svg circle {
margin: 0 auto;
fill: none;
stroke-width: 20;
stroke-dasharray: 630;
stroke-linecap: round;
}

.mdhui-progress-ring circle:nth-child(2) {
.mdhui-progress-ring > svg circle:nth-child(n+2) {
stroke: var(--mdhui-color-primary);
animation-fill-mode: forwards;
}
Expand Down

This file was deleted.

152 changes: 68 additions & 84 deletions src/components/presentational/ProgressRing/ProgressRing.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,97 +1,81 @@
import React from 'react';
import { Layout } from '../../presentational';
import ProgressRing, { ProgressRingProps } from './ProgressRing';
import './ProgressRing.stories.css';
import ProgressRing from './ProgressRing';
import { StoryObj } from '@storybook/react';
import { argTypesToHide } from '../../../../.storybook/helpers';

type ProgressRingStoryArgs = React.ComponentProps<typeof ProgressRing> & {
colorScheme: 'auto' | 'light' | 'dark';
};

export default {
title: 'Presentational/ProgressRing',
component: ProgressRing,
parameters: {layout: 'fullscreen'}
};

const render = (args: ProgressRingProps) => <Layout colorScheme="auto"><ProgressRing {...args} /></Layout>

const children = <div className="progress-ring-story">Great Job!</div>;

export const Default = {
args: {
children: children
},
render: render
};

export const OneThirdDone = {
args: {
children: children,
percentCompleted: 33
},
render: render
};

export const TwoThirdsDone = {
args: {
children: children,
percentCompleted: 66
},
render: render
};

export const Done = {
args: {
children: children,
percentCompleted: 100
},
render: render
};

export const DifferentColor = {
args: {
children: children,
color: '#71b345'
},
render: render
};

export const Default_Animated = {
args: {
children: children,
animate: true
},
render: render
};

export const OneThirdDone_Animated = {
args: {
children: children,
percentCompleted: 33,
animate: true
},
render: render
};

export const TwoThirdsDone_Animated = {
args: {
children: children,
percentCompleted: 66,
animate: true
},
render: render
parameters: { layout: 'fullscreen' },
render: (args: ProgressRingStoryArgs) => {
return <Layout colorScheme={args.colorScheme}>
<ProgressRing {...args}>
<div style={{ fontWeight: 'bold', fontSize: '28px' }}>Great Job!</div>
</ProgressRing>
</Layout>;
}
};

export const Done_Animated = {
export const Default: StoryObj<ProgressRingStoryArgs> = {
args: {
children: children,
colorScheme: 'auto',
diameter: 220,
strokeWidth: 20,
color: undefined,
incompleteColor: undefined,
percentCompleted: 100,
animate: true
},
render: render
};

export const DifferentColor_Animated = {
args: {
children: children,
color: '#71b345',
animate: true
},
render: render
argTypes: {
colorScheme: {
name: 'color scheme',
control: 'radio',
options: ['auto', 'light', 'dark']
},
diameter: {
name: 'diameter',
control: {
type: 'range',
min: 40,
max: 400,
step: 1
}
},
strokeWidth: {
name: 'strokeWidth',
control: {
type: 'range',
min: 1,
max: 40,
step: 1
}
},
color: {
name: 'color',
control: 'color'
},
incompleteColor: {
name: 'incomplete color',
control: 'color'
},
percentCompleted: {
name: 'percent completed',
control: {
type: 'range',
min: 0,
max: 100,
step: 1
}
},
animate: {
name: 'animate',
control: 'boolean'
},
...argTypesToHide(['style', 'innerRef'])
}
};
86 changes: 66 additions & 20 deletions src/components/presentational/ProgressRing/ProgressRing.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,67 @@
import React, { CSSProperties, useContext } from 'react'
import './ProgressRing.css'
import { ColorDefinition, resolveColor } from "../../../helpers/colors";
import { LayoutContext } from "../Layout";
import React, { CSSProperties, ReactNode, Ref, useContext } from 'react';
import './ProgressRing.css';
import { ColorDefinition, resolveColor } from '../../../helpers';
import { LayoutContext } from '../Layout';
import { v4 as uuid } from 'uuid';

export interface ProgressRingProps {
children: React.ReactNode;
style?: CSSProperties;
diameter?: number;
strokeWidth?: number;
color?: ColorDefinition;
incompleteColor?: ColorDefinition;
percentCompleted?: number;
animate?: boolean;
style?: CSSProperties;
children: ReactNode;
innerRef?: Ref<HTMLDivElement>;
}

export default function (props: ProgressRingProps) {
export default function ProgressRing(props: ProgressRingProps) {
const context = useContext(LayoutContext);

const diameter = props.diameter ?? 220;
const strokeWidth = props.strokeWidth ?? 20;

const circleDiameter = diameter - strokeWidth;
const circleCircumference = circleDiameter * Math.PI;

let offset = 0;
if (props.percentCompleted) {
offset = 630 - ((props.percentCompleted / 100.0) * 630);
if (props.percentCompleted !== undefined) {
offset = circleCircumference - ((props.percentCompleted / 100.0) * circleCircumference);
}

const circleStyle = {
const ringStyle = {
width: `${diameter}px`,
height: `${diameter}px`,
'--mdhui-progress-ring-animation-start': circleCircumference
} as CSSProperties;

const svgStyle = {
width: `${diameter}px`,
height: `${diameter}px`,
overflow: 'visible'
} as CSSProperties;

const backgroundCircleStyle = {
strokeWidth: strokeWidth,
strokeDasharray: circleCircumference,
stroke: resolveColor(context.colorScheme, props.incompleteColor) ?? 'transparent'
} as CSSProperties;

const foregroundCircleMaskId = uuid();
const foregroundCircleMaskStyle = {
strokeWidth: strokeWidth + 2,
strokeDasharray: circleCircumference,
stroke: 'black',
strokeDashoffset: offset,
strokeLinecap: 'butt',
animation: props.animate ? 'mdhui-progress-ring 750ms ease-in-out' : undefined,
'--mdhui-progress-ring-animation-end': offset
} as CSSProperties;

const foregroundCircleStyle = {
strokeWidth: strokeWidth,
strokeDasharray: circleCircumference,
stroke: resolveColor(context.colorScheme, props.color),
strokeDashoffset: offset,
animation: props.animate ? 'mdhui-progress-ring 750ms ease-in-out' : undefined,
Expand All @@ -33,15 +75,19 @@ export default function (props: ProgressRingProps) {
animationFillMode: 'forwards'
} as CSSProperties;

return (
<div className="mdhui-progress-ring" style={props.style}>
<svg>
<circle cx="50%" cy="50%" r="100"/>
<circle cx="50%" cy="50%" r="100" style={circleStyle}/>
</svg>
<div className="mdhui-progress-ring-content" style={contentStyle}>
{props.children}
</div>
return <div className="mdhui-progress-ring" style={{ ...ringStyle, ...props.style }} ref={props.innerRef}>
<svg style={svgStyle}>
{offset != circleCircumference &&
<mask id={foregroundCircleMaskId} x={-strokeWidth} y={-strokeWidth} width={diameter + strokeWidth} height={diameter + strokeWidth}>
<rect x="0" y="0" width="100%" height="100%" fill="white" />
<circle cx="50%" cy="50%" r={circleDiameter / 2} style={foregroundCircleMaskStyle} />
</mask>
}
<circle cx="50%" cy="50%" r={circleDiameter / 2} style={backgroundCircleStyle} mask={`url(#${foregroundCircleMaskId})`} />
<circle cx="50%" cy="50%" r={circleDiameter / 2} style={foregroundCircleStyle} />
</svg>
<div className="mdhui-progress-ring-content" style={contentStyle}>
{props.children}
</div>
);
</div>;
}
Loading