Skip to content

Commit

Permalink
feat(ProgressCircular): new component (#1011)
Browse files Browse the repository at this point in the history
* feat(ProgressCircular): new component

* fix: tokens

* fix: comments

* fix: snapshots
  • Loading branch information
savutsang authored Oct 28, 2024
1 parent b32b193 commit 967abcf
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { renderWithTheme } from '../../test-utils/renderer';
import { ProgressCircular } from './progress-circular';

describe('ProgressCircular', () => {
test('Matches the snapshot', () => {
const tree = renderWithTheme(
<ProgressCircular value={66} />,
);

expect(tree).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ProgressCircular Matches the snapshot 1`] = `
.c0 {
-webkit-transition: stroke-dashoffset 850ms ease;
transition: stroke-dashoffset 850ms ease;
}
<svg
aria-hidden="true"
focusable="false"
height="32"
viewBox="0 0 64 64"
width="32"
>
<circle
class="c0"
cx="32"
cy="32"
fill="none"
r="28"
stroke="#006296"
stroke-dasharray="175.92918860102841"
stroke-dashoffset="59.81592412434966"
stroke-linecap="round"
stroke-width="8"
transform="rotate(-90 32 32)"
/>
</svg>
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { VoidFunctionComponent } from 'react';
import styled, { useTheme } from 'styled-components';

const sizes = {
xsmall: 16,
small: 24,
medium: 32,
large: 64,
};

type Size = keyof typeof sizes;

const VIEWBOX = 64;
const STROKE_WIDTH = 8;
const RADIUS = (VIEWBOX - STROKE_WIDTH) / 2;
const CENTER_XY = VIEWBOX / 2;
const CIRCUMFERENCE = RADIUS * 2 * Math.PI;

const Circle = styled.circle`
transition: stroke-dashoffset 850ms ease;
`;

export interface ProgressCircularProps {
className?: string;
size?: Size;
inverted?: boolean;
value: number;
}

// Source: https://css-tricks.com/building-progress-ring-quickly/
export const ProgressCircular: VoidFunctionComponent<ProgressCircularProps> = ({
className,
inverted = false,
size = 'medium',
value,
}) => {
const theme = useTheme();
const strokeDashoffset = (1 - (value / 100)) * CIRCUMFERENCE;

return (
<svg
width={sizes[size]}
height={sizes[size]}
viewBox={`0 0 ${VIEWBOX} ${VIEWBOX}`}
className={className}
aria-hidden="true"
focusable="false"
>
<Circle
cx={CENTER_XY}
cy={CENTER_XY}
r={RADIUS}
stroke={inverted
? theme.component['progress-circular-inverted-color']
: theme.component['progress-circular-color']}
strokeWidth={STROKE_WIDTH}
fill="none"
strokeDasharray={CIRCUMFERENCE}
strokeDashoffset={strokeDashoffset}
strokeLinecap="round"
transform={`rotate(-90 ${CENTER_XY} ${CENTER_XY})`}
/>
</svg>
);
};
1 change: 1 addition & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export { NumericInput } from './components/numeric-input/numeric-input';
export { PasswordCreationInput } from './components/password-creation-input/password-creation-input';
export { PasswordInput } from './components/password-input/password-input';
export { PhoneInput } from './components/phone-input/phone-input';
export { ProgressCircular, ProgressCircularProps } from './components/progress-circular/progress-circular';
export { RadioButtonGroup } from './components/radio-button-group/radio-button-group';
export { SearchContextual } from './components/search/search-contextual';
export { SearchGlobal } from './components/search/search-global';
Expand Down
3 changes: 3 additions & 0 deletions packages/react/src/themes/tokens/component-tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { defaultMenuTokens, MenuTokens } from './component/menu-tokens';
import { defaultNumericInputTokens, NumericInputTokens } from './component/numeric-input-tokens';
import { defaultPasswordInputTokens, PasswordInputTokens } from './component/password-input-tokens';
import { defaultPhoneInputTokens, PhoneInputTokens } from './component/phone-input-tokens';
import { defaultProgressCircularTokens, ProgressCircularTokens } from './component/progress-circular-tokens';
import { defaultRadioButtonGroupTokens, RadioButtonGroupTokens } from './component/radio-button-group-tokens';
import { defaultRadioCardTokens, RadioCardTokens } from './component/radio-card-tokens';
import { defaultSearchInputTokens, SearchInputTokens } from './component/search-input-tokens';
Expand Down Expand Up @@ -85,6 +86,7 @@ export type ComponentTokens =
| NavListTokens
| PaginationTokens
| ProgressTokens
| ProgressCircularTokens
| LinkTokens
| BadgeTokens
| GlobalBannerTokens
Expand Down Expand Up @@ -125,6 +127,7 @@ export const defaultComponentTokens: ComponentTokenMap = {
...defaultNavListTokens,
...defaultPaginationTokens,
...defaultProgressTokens,
...defaultProgressCircularTokens,
...defaultLinkTokens,
...defaultCheckboxTokens,
...defaultChooserTokens,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { AliasTokens } from '../alias-tokens';
import { RefTokens } from '../ref-tokens';

export type ProgressCircularTokens =
| 'progress-circular-color'
| 'progress-circular-inverted-color'

export type ProgressCircularTokenValue = AliasTokens | RefTokens;

export type ProgressCircularTokenMap = {
[Token in ProgressCircularTokens]: ProgressCircularTokenValue;
};

export const defaultProgressCircularTokens: ProgressCircularTokenMap = {
'progress-circular-color': 'color-background-brand',
'progress-circular-inverted-color': 'color-white',
};
47 changes: 47 additions & 0 deletions packages/storybook/stories/progress-circular.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { ProgressCircular } from '@equisoft/design-elements-react';
import { Meta, StoryObj } from '@storybook/react';

const meta: Meta<typeof ProgressCircular> = {
title: 'Components/Progress Circular',
component: ProgressCircular,
args: {
size: 'medium',
inverted: false,
},
argTypes: {
className: {
control: {
type: 'text',
},
},
inverted: {
control: {
type: 'boolean',
},
},
value: {
control: {
type: 'range',
min: 0,
max: 100,
},
},
size: {
control: {
type: 'select',
options: ['xsmall', 'small', 'medium', 'large'],
},
},
},
};

export default meta;

type Story = StoryObj<typeof ProgressCircular>;

export const Default: Story = {
args: {
value: 60,
inverted: false,
},
};

0 comments on commit 967abcf

Please sign in to comment.