diff --git a/packages/configs/eslint-config/index.js b/packages/configs/eslint-config/index.js
index 304513900..76b3666c8 100644
--- a/packages/configs/eslint-config/index.js
+++ b/packages/configs/eslint-config/index.js
@@ -77,6 +77,7 @@ export default tseslint.config(
'Button',
'ButtonBase',
'Checkbox',
+ 'CircularProgress',
'Dialog',
'DialogActions',
'DialogContent',
@@ -146,6 +147,10 @@ export default tseslint.config(
group: ['@mui/material/Checkbox'],
importNames: ['default'],
},
+ {
+ group: ['@mui/material/CircularProgress'],
+ importNames: ['default'],
+ },
{
group: ['@mui/material/Dialog'],
importNames: ['default'],
diff --git a/packages/react/src/components/CircularProgress/CircularProgress.api.mdx b/packages/react/src/components/CircularProgress/CircularProgress.api.mdx
new file mode 100644
index 000000000..92f6f1038
--- /dev/null
+++ b/packages/react/src/components/CircularProgress/CircularProgress.api.mdx
@@ -0,0 +1,37 @@
+import { Meta } from '@storybook/addon-docs';
+import LinkTo from '@storybook/addon-links/react';
+import { TableInterface } from '~storybook/components/TableInterface';
+
+
+
+# CircularProgress API
+
+```js
+import { CircularProgress } from '@elonkit/react';
+```
+
+## Component name
+
+The name `ESCircularProgress` can be used when providing default props or style overrides in the theme.
+
+## Props
+
+
+
+
+
+## CSS
+
+
+
+
+
+## Demos
+
+
+ -
+
+
CircularProgress
+
+
+
diff --git a/packages/react/src/components/CircularProgress/CircularProgress.classes.ts b/packages/react/src/components/CircularProgress/CircularProgress.classes.ts
new file mode 100644
index 000000000..50b4b6e80
--- /dev/null
+++ b/packages/react/src/components/CircularProgress/CircularProgress.classes.ts
@@ -0,0 +1,53 @@
+import { generateUtilityClass, generateUtilityClasses } from '@mui/material';
+
+export interface CircularProgressClasses {
+ /** Styles applied to the root element. */
+ root: string;
+ /** Styles applied to the root element if `variant="determinate"`. */
+ determinate: string;
+ /** Styles applied to the root element if `variant="indeterminate"`. */
+ indeterminate: string;
+ /** Styles applied to the root element if `color="primary"`. */
+ colorPrimary: string;
+ /** Styles applied to the root element if `color="secondary"`. */
+ colorSecondary: string;
+ /** Styles applied to the svg element. */
+ svg: string;
+ /** Styles applied to the `circle` svg path. */
+ circle: string;
+ /** Styles applied to the `circle` svg path if `variant="determinate"`.
+ * @deprecated Combine the [.MuiCircularProgress-circle](/material-ui/api/circular-progress/#circular-progress-classes-circle) and [.MuiCircularProgress-determinate](/material-ui/api/circular-progress/#circular-progress-classes-determinate) classes instead. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details.
+ */
+ circleDeterminate: string;
+ /** Styles applied to the `circle` svg path if `variant="indeterminate"`.
+ * @deprecated Combine the [.MuiCircularProgress-circle](/material-ui/api/circular-progress/#circular-progress-classes-circle) and [.MuiCircularProgress-indeterminate](/material-ui/api/circular-progress/#circular-progress-classes-indeterminate) classes instead. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details.
+ */
+ circleIndeterminate: string;
+ /** Styles applied to the `circle` svg path if `disableShrink={true}`. */
+ circleDisableShrink: string;
+ /** Styles applied to the background `circle` svg path. */
+ background: string;
+ /** Styles applied to the content. */
+ content: string;
+}
+
+export type CircularProgressClassKey = keyof CircularProgressClasses;
+
+export function getCircularProgressUtilityClass(slot: string): string {
+ return generateUtilityClass('MuiCircularProgress', slot);
+}
+
+export const circularProgressClasses: CircularProgressClasses = generateUtilityClasses('MuiCircularProgress', [
+ 'root',
+ 'determinate',
+ 'indeterminate',
+ 'colorPrimary',
+ 'colorSecondary',
+ 'svg',
+ 'circle',
+ 'circleDeterminate',
+ 'circleIndeterminate',
+ 'circleDisableShrink',
+ 'background',
+ 'content',
+]);
diff --git a/packages/react/src/components/CircularProgress/CircularProgress.stories.tsx b/packages/react/src/components/CircularProgress/CircularProgress.stories.tsx
new file mode 100644
index 000000000..86b9a2ecf
--- /dev/null
+++ b/packages/react/src/components/CircularProgress/CircularProgress.stories.tsx
@@ -0,0 +1,49 @@
+import { Meta, StoryObj } from '@storybook/react';
+
+import { Typography } from '@mui/material';
+
+import { CircularProgress } from '.';
+
+const meta: Meta = {
+ tags: ['autodocs'],
+ component: CircularProgress,
+ parameters: {
+ references: ['CircularProgress'],
+ },
+ argTypes: {
+ variant: {
+ options: ['determinate', 'indeterminate'],
+ control: {
+ type: 'select',
+ },
+ },
+ color: {
+ options: ['primary', 'secondary', 'error', 'info', 'success', 'warning', 'inherit'],
+ control: {
+ type: 'select',
+ },
+ },
+ },
+ args: {
+ variant: 'indeterminate',
+ color: 'primary',
+ value: 20,
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Demo: Story = {
+ render: (args) => {
+ return (
+
+ {`${Math.round(args.value ?? 0)}%`}
+
+ );
+ },
+};
diff --git a/packages/react/src/components/CircularProgress/CircularProgress.tsx b/packages/react/src/components/CircularProgress/CircularProgress.tsx
new file mode 100644
index 000000000..f1dad3772
--- /dev/null
+++ b/packages/react/src/components/CircularProgress/CircularProgress.tsx
@@ -0,0 +1,231 @@
+import { forwardRef } from 'react';
+
+import { CircularProgressProps } from './CircularProgress.types';
+
+import clsx from 'clsx';
+import { getCircularProgressUtilityClass } from './CircularProgress.classes';
+
+import { unstable_composeClasses as composeClasses } from '@mui/base';
+
+import { styled, useThemeProps } from '@mui/material/styles';
+import { css, keyframes } from '@mui/system';
+import { capitalize } from '@mui/material';
+
+const SIZE = 44;
+
+const circularRotateKeyframe = keyframes`
+ 0% {
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
+`;
+
+const circularDashKeyframe = keyframes`
+ 0% {
+ stroke-dasharray: 1px, 200px;
+ stroke-dashoffset: 0;
+ }
+
+ 50% {
+ stroke-dasharray: 100px, 200px;
+ stroke-dashoffset: -15px;
+ }
+
+ 100% {
+ stroke-dasharray: 100px, 200px;
+ stroke-dashoffset: -125px;
+ }
+`;
+
+type CircularProgressOwnerState = {
+ classes?: CircularProgressProps['classes'];
+ variant: NonNullable;
+ color: NonNullable;
+ disableShrink: NonNullable;
+ thickness: NonNullable;
+ value: NonNullable;
+};
+
+const useUtilityClasses = (ownerState: CircularProgressOwnerState) => {
+ const { classes, variant, color, disableShrink } = ownerState;
+
+ const slots = {
+ root: ['root', variant, `color${capitalize(color)}`],
+ svg: ['svg'],
+ circle: ['circle', `circle${capitalize(variant)}`, disableShrink && 'circleDisableShrink'],
+ background: ['background'],
+ content: ['content'],
+ };
+
+ return composeClasses(slots, getCircularProgressUtilityClass, classes);
+};
+
+const CircularProgressRoot = styled('span', {
+ name: 'ESCircularProgress',
+ slot: 'Root',
+ overridesResolver: (props, styles) => {
+ const { ownerState } = props;
+
+ return [styles.root, styles[ownerState.variant], styles[`color${capitalize(ownerState.color)}`]];
+ },
+})<{ ownerState: CircularProgressOwnerState }>(({ ownerState: { color, variant }, theme }) => ({
+ display: 'inline-block',
+ color: theme.vars.palette[color][300],
+ position: 'relative',
+
+ ...(variant === 'determinate' && {
+ transition: theme.transitions.create('transform'),
+ }),
+}));
+
+const CircularProgressSVG = styled('svg', {
+ name: 'ESCircularProgress',
+ slot: 'Svg',
+ overridesResolver: (props, styles) => {
+ return styles.svg;
+ },
+})<{ ownerState: CircularProgressOwnerState }>(({ ownerState: { variant } }) => ({
+ transform: 'rotate(-90deg)',
+ display: 'block',
+ ...(variant === 'indeterminate' && {
+ animation: `${circularRotateKeyframe} 1.4s linear infinite`,
+ }),
+}));
+
+const CircularProgressCircle = styled('circle', {
+ name: 'ESCircularProgress',
+ slot: 'Circle',
+ overridesResolver: (props, styles) => {
+ const { ownerState } = props;
+
+ return [
+ styles.circle,
+ styles[`circle${capitalize(ownerState.variant)}`],
+ ownerState.disableShrink && styles.circleDisableShrink,
+ ];
+ },
+})<{ ownerState: CircularProgressOwnerState }>(
+ ({ ownerState: { variant, thickness, value }, theme }) => ({
+ stroke: 'currentColor',
+ ...(variant === 'determinate' && {
+ transition: theme.transitions.create('stroke-dashoffset'),
+ strokeDasharray: (2 * Math.PI * ((SIZE - thickness) / 2)).toFixed(3),
+ strokeDashoffset: `${(((100 - value) / 100) * (2 * Math.PI * ((SIZE - thickness) / 2))).toFixed(3)}px`,
+ }),
+ ...(variant === 'indeterminate' && {
+ strokeDasharray: '80px, 200px',
+ strokeDashoffset: 0,
+ animation: `${circularDashKeyframe} 1.4s ease-in-out infinite`,
+ }),
+ }),
+ ({ ownerState }) =>
+ ownerState.variant === 'indeterminate' &&
+ css`
+ animation: ${circularDashKeyframe} 1.4s ease-in-out infinite;
+ `
+);
+
+const CircularProgressBackground = styled('circle', {
+ name: 'ESCircularProgress',
+ slot: 'Background',
+ overridesResolver: (props, styles) => styles.background,
+})<{ ownerState: CircularProgressOwnerState }>(({ ownerState: { color }, theme }) => ({
+ stroke: theme.vars.palette[color].A400,
+ transition: theme.transitions.create('stroke-dashoffset'),
+}));
+
+const CircularProgressContent = styled('div', {
+ name: 'ESCircularProgress',
+ slot: 'Content',
+ overridesResolver: (props, styles) => styles.content,
+})(() => ({
+ top: 0,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ position: 'absolute',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+}));
+
+export const CircularProgress = forwardRef(
+ function CircularProgress(inProps, ref) {
+ const props = useThemeProps({ props: inProps, name: 'ESCircularProgress' });
+ const {
+ children,
+ className,
+ color = 'primary',
+ disableShrink = false,
+ size = 40,
+ thickness = 3.6,
+ value = 0,
+ variant = 'indeterminate',
+ ...other
+ } = props;
+
+ const ownerState = {
+ color,
+ disableShrink,
+ size,
+ thickness,
+ value,
+ variant,
+ ...props,
+ };
+
+ const classes = useUtilityClasses(ownerState);
+
+ // const circleStyle = {};
+ // const rootStyle = {};
+ // const rootProps = {};
+
+ // if (variant === 'determinate') {
+ // const circumference = 2 * Math.PI * ((SIZE - thickness) / 2);
+ // circleStyle.strokeDasharray = circumference.toFixed(3);
+ // circleStyle.strokeDashoffset = `${(((100 - value) / 100) * circumference).toFixed(3)}px`;
+ // rootStyle.transform = 'rotate(-90deg)';
+ // }
+
+ return (
+
+
+
+
+
+
+ {!!children && {children}}
+
+ );
+ }
+);
diff --git a/packages/react/src/components/CircularProgress/CircularProgress.types.ts b/packages/react/src/components/CircularProgress/CircularProgress.types.ts
new file mode 100644
index 000000000..5bf5eed6d
--- /dev/null
+++ b/packages/react/src/components/CircularProgress/CircularProgress.types.ts
@@ -0,0 +1,65 @@
+/* eslint-disable @typescript-eslint/no-empty-object-type */
+
+import { ReactNode } from 'react';
+
+import { CircularProgressClasses } from './CircularProgress.classes';
+
+import { SxProps, Theme } from '@mui/material';
+
+import { OverridableStringUnion } from '@mui/types';
+
+export interface CircularProgressPropsColorOverrides {}
+
+export interface CircularProgressProps {
+ children: ReactNode;
+ /**
+ * Override or extend the styles applied to the component.
+ */
+ classes?: Partial;
+
+ /** Class applied to the root element. */
+ className?: string;
+
+ /**
+ * The color of the component.
+ * @default 'primary'
+ */
+ color?: OverridableStringUnion<
+ 'primary' | 'secondary' | 'error' | 'info' | 'success' | 'warning',
+ CircularProgressPropsColorOverrides
+ >;
+ /**
+ * If `true`, the shrink animation is disabled.
+ * This only works if variant is `indeterminate`.
+ * @default false
+ */
+ disableShrink?: boolean;
+ /**
+ * The size of the component.
+ * If using a number, the pixel unit is assumed.
+ * If using a string, you need to provide the CSS unit, for example '3rem'.
+ * @default 40
+ */
+ size?: number | string;
+ /**
+ * The system prop that allows defining system overrides as well as additional CSS styles.
+ */
+ sx?: SxProps;
+ /**
+ * The thickness of the circle.
+ * @default 3.6
+ */
+ thickness?: number;
+ /**
+ * The value of the progress indicator for the determinate variant.
+ * Value between 0 and 100.
+ * @default 0
+ */
+ value?: number;
+ /**
+ * The variant to use.
+ * Use indeterminate when there is no progress value.
+ * @default 'indeterminate'
+ */
+ variant?: 'determinate' | 'indeterminate';
+}
diff --git a/packages/react/src/components/CircularProgress/index.ts b/packages/react/src/components/CircularProgress/index.ts
new file mode 100644
index 000000000..ff62e5458
--- /dev/null
+++ b/packages/react/src/components/CircularProgress/index.ts
@@ -0,0 +1,3 @@
+export { CircularProgress } from './CircularProgress';
+export { CircularProgressClasses, circularProgressClasses, CircularProgressClassKey } from './CircularProgress.classes';
+export { CircularProgressProps } from './CircularProgress.types';
diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts
index df146bfcb..80d54f29a 100644
--- a/packages/react/src/components/index.ts
+++ b/packages/react/src/components/index.ts
@@ -17,6 +17,7 @@ export * from './Calendar';
export * from './Checkbox';
export * from './Chip';
export * from './Chips';
+export * from './CircularProgress';
export * from './DateAdapter';
export * from './Dialog';
export * from './DialogStack';
diff --git a/packages/react/src/overrides.d.ts b/packages/react/src/overrides.d.ts
index 2e751aa35..a0a159a35 100644
--- a/packages/react/src/overrides.d.ts
+++ b/packages/react/src/overrides.d.ts
@@ -73,6 +73,7 @@ import {
import { CheckboxClassKey, CheckboxProps, CheckboxIconClassKey, CheckboxIconProps } from './components/Checkbox';
import { ChipClassKey, ChipProps } from './components/Chip';
import { ChipsClassKey, ChipsProps } from './components/Chips';
+import { CircularProgressClassKey, CircularProgressProps } from './components/CircularProgress';
import {
DialogActionsClassKey,
DialogActionsProps,
@@ -366,6 +367,7 @@ declare module '@mui/material/styles/props' {
ESCheckboxIcon: CheckboxIconProps;
ESChip: ChipProps;
ESChips: ChipsProps;
+ ESCircularProgress: CircularProgressProps;
ESDialog: DialogProps;
ESDialogActions: DialogActionsProps;
ESDialogArrow: DialogArrowProps;
@@ -505,6 +507,7 @@ declare module '@mui/material/styles/overrides' {
ESCheckboxIcon: CheckboxIconClassKey;
ESChip: ChipClassKey;
ESChips: ChipsClassKey;
+ ESCircularProgress: CircularProgressClassKey;
ESDialog: DialogClassKey;
ESDialogActions: DialogActionsClassKey;
ESDialogArrow: DialogArrowClassKey;
@@ -733,6 +736,10 @@ declare module '@mui/material/styles/components' {
defaultProps?: ComponentsProps['ESChips'];
styleOverrides?: ComponentsOverrides['ESChips'];
};
+ ESCircularProgress?: {
+ defaultProps?: ComponentsProps['ESCircularProgress'];
+ styleOverrides?: ComponentsOverrides['ESCircularProgress'];
+ };
ESDialog?: {
defaultProps?: ComponentsProps['ESDialog'];
styleOverrides?: ComponentsOverrides['ESDialog'];