From 2446dceb696973c36c387916032eb69d28a7a46a Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Thu, 27 Oct 2022 14:03:19 +0100 Subject: [PATCH] [Joy] Add `LinearProgress` component (#34514) --- .../linear-progress/LinearProgressColors.js | 62 ++++ .../linear-progress/LinearProgressColors.tsx | 63 +++++ .../LinearProgressDeterminate.js | 27 ++ .../LinearProgressDeterminate.tsx | 27 ++ .../LinearProgressDeterminate.tsx.preview | 5 + .../linear-progress/LinearProgressSizes.js | 13 + .../linear-progress/LinearProgressSizes.tsx | 13 + .../LinearProgressSizes.tsx.preview | 3 + .../LinearProgressThickness.js | 6 + .../LinearProgressThickness.tsx | 6 + .../LinearProgressThickness.tsx.preview | 1 + .../linear-progress/LinearProgressUsage.js | 46 +++ .../LinearProgressVariables.js | 35 +++ .../LinearProgressVariables.tsx | 35 +++ .../linear-progress/LinearProgressVariants.js | 14 + .../LinearProgressVariants.tsx | 14 + .../LinearProgressVariants.tsx.preview | 4 + .../LinearProgressWithLabel.js | 43 +++ .../LinearProgressWithLabel.tsx | 43 +++ .../linear-progress/linear-progress.md | 83 ++++++ docs/data/joy/pages.ts | 1 + docs/pages/joy-ui/react-linear-progress.js | 7 + .../LinearProgress/LinearProgress.test.tsx | 74 +++++ .../src/LinearProgress/LinearProgress.tsx | 266 ++++++++++++++++++ .../src/LinearProgress/LinearProgressProps.ts | 64 +++++ packages/mui-joy/src/LinearProgress/index.ts | 4 + .../LinearProgress/linearProgressClasses.ts | 63 +++++ packages/mui-joy/src/styles/components.d.ts | 9 + .../mui-joy/src/styles/extendTheme.spec.ts | 19 +- 29 files changed, 1049 insertions(+), 1 deletion(-) create mode 100644 docs/data/joy/components/linear-progress/LinearProgressColors.js create mode 100644 docs/data/joy/components/linear-progress/LinearProgressColors.tsx create mode 100644 docs/data/joy/components/linear-progress/LinearProgressDeterminate.js create mode 100644 docs/data/joy/components/linear-progress/LinearProgressDeterminate.tsx create mode 100644 docs/data/joy/components/linear-progress/LinearProgressDeterminate.tsx.preview create mode 100644 docs/data/joy/components/linear-progress/LinearProgressSizes.js create mode 100644 docs/data/joy/components/linear-progress/LinearProgressSizes.tsx create mode 100644 docs/data/joy/components/linear-progress/LinearProgressSizes.tsx.preview create mode 100644 docs/data/joy/components/linear-progress/LinearProgressThickness.js create mode 100644 docs/data/joy/components/linear-progress/LinearProgressThickness.tsx create mode 100644 docs/data/joy/components/linear-progress/LinearProgressThickness.tsx.preview create mode 100644 docs/data/joy/components/linear-progress/LinearProgressUsage.js create mode 100644 docs/data/joy/components/linear-progress/LinearProgressVariables.js create mode 100644 docs/data/joy/components/linear-progress/LinearProgressVariables.tsx create mode 100644 docs/data/joy/components/linear-progress/LinearProgressVariants.js create mode 100644 docs/data/joy/components/linear-progress/LinearProgressVariants.tsx create mode 100644 docs/data/joy/components/linear-progress/LinearProgressVariants.tsx.preview create mode 100644 docs/data/joy/components/linear-progress/LinearProgressWithLabel.js create mode 100644 docs/data/joy/components/linear-progress/LinearProgressWithLabel.tsx create mode 100644 docs/data/joy/components/linear-progress/linear-progress.md create mode 100644 docs/pages/joy-ui/react-linear-progress.js create mode 100644 packages/mui-joy/src/LinearProgress/LinearProgress.test.tsx create mode 100644 packages/mui-joy/src/LinearProgress/LinearProgress.tsx create mode 100644 packages/mui-joy/src/LinearProgress/LinearProgressProps.ts create mode 100644 packages/mui-joy/src/LinearProgress/index.ts create mode 100644 packages/mui-joy/src/LinearProgress/linearProgressClasses.ts diff --git a/docs/data/joy/components/linear-progress/LinearProgressColors.js b/docs/data/joy/components/linear-progress/LinearProgressColors.js new file mode 100644 index 00000000000000..51ca565134bbdd --- /dev/null +++ b/docs/data/joy/components/linear-progress/LinearProgressColors.js @@ -0,0 +1,62 @@ +import * as React from 'react'; +import LinearProgress from '@mui/joy/LinearProgress'; +import Box from '@mui/joy/Box'; +import Radio from '@mui/joy/Radio'; +import RadioGroup from '@mui/joy/RadioGroup'; +import Sheet from '@mui/joy/Sheet'; +import Stack from '@mui/joy/Stack'; +import Typography from '@mui/joy/Typography'; + +export default function LinearProgressColors() { + const [variant, setVariant] = React.useState('soft'); + + return ( + + + + + + + + + + + + Variant: + + setVariant(event.target.value)} + > + + + + + + + + ); +} diff --git a/docs/data/joy/components/linear-progress/LinearProgressColors.tsx b/docs/data/joy/components/linear-progress/LinearProgressColors.tsx new file mode 100644 index 00000000000000..852dfcce4db0f8 --- /dev/null +++ b/docs/data/joy/components/linear-progress/LinearProgressColors.tsx @@ -0,0 +1,63 @@ +import * as React from 'react'; +import LinearProgress from '@mui/joy/LinearProgress'; +import Box from '@mui/joy/Box'; +import Radio from '@mui/joy/Radio'; +import RadioGroup from '@mui/joy/RadioGroup'; +import Sheet from '@mui/joy/Sheet'; +import Stack from '@mui/joy/Stack'; +import Typography from '@mui/joy/Typography'; +import { VariantProp } from '@mui/joy/styles'; + +export default function LinearProgressColors() { + const [variant, setVariant] = React.useState('soft'); + + return ( + + + + + + + + + + + + Variant: + + setVariant(event.target.value as VariantProp)} + > + + + + + + + + ); +} diff --git a/docs/data/joy/components/linear-progress/LinearProgressDeterminate.js b/docs/data/joy/components/linear-progress/LinearProgressDeterminate.js new file mode 100644 index 00000000000000..08c4f9fc7edb98 --- /dev/null +++ b/docs/data/joy/components/linear-progress/LinearProgressDeterminate.js @@ -0,0 +1,27 @@ +import * as React from 'react'; +import Stack from '@mui/joy/Stack'; +import LinearProgress from '@mui/joy/LinearProgress'; + +export default function LinearProgressDeterminate() { + const [progress, setProgress] = React.useState(0); + + React.useEffect(() => { + const timer = setInterval(() => { + setProgress((prevProgress) => (prevProgress >= 100 ? 0 : prevProgress + 10)); + }, 800); + + return () => { + clearInterval(timer); + }; + }, []); + + return ( + + + + + + + + ); +} diff --git a/docs/data/joy/components/linear-progress/LinearProgressDeterminate.tsx b/docs/data/joy/components/linear-progress/LinearProgressDeterminate.tsx new file mode 100644 index 00000000000000..08c4f9fc7edb98 --- /dev/null +++ b/docs/data/joy/components/linear-progress/LinearProgressDeterminate.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import Stack from '@mui/joy/Stack'; +import LinearProgress from '@mui/joy/LinearProgress'; + +export default function LinearProgressDeterminate() { + const [progress, setProgress] = React.useState(0); + + React.useEffect(() => { + const timer = setInterval(() => { + setProgress((prevProgress) => (prevProgress >= 100 ? 0 : prevProgress + 10)); + }, 800); + + return () => { + clearInterval(timer); + }; + }, []); + + return ( + + + + + + + + ); +} diff --git a/docs/data/joy/components/linear-progress/LinearProgressDeterminate.tsx.preview b/docs/data/joy/components/linear-progress/LinearProgressDeterminate.tsx.preview new file mode 100644 index 00000000000000..326f93ef8f184a --- /dev/null +++ b/docs/data/joy/components/linear-progress/LinearProgressDeterminate.tsx.preview @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/docs/data/joy/components/linear-progress/LinearProgressSizes.js b/docs/data/joy/components/linear-progress/LinearProgressSizes.js new file mode 100644 index 00000000000000..2998a1b003912a --- /dev/null +++ b/docs/data/joy/components/linear-progress/LinearProgressSizes.js @@ -0,0 +1,13 @@ +import * as React from 'react'; +import Stack from '@mui/joy/Stack'; +import LinearProgress from '@mui/joy/LinearProgress'; + +export default function LinearProgressColors() { + return ( + + + + + + ); +} diff --git a/docs/data/joy/components/linear-progress/LinearProgressSizes.tsx b/docs/data/joy/components/linear-progress/LinearProgressSizes.tsx new file mode 100644 index 00000000000000..2998a1b003912a --- /dev/null +++ b/docs/data/joy/components/linear-progress/LinearProgressSizes.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; +import Stack from '@mui/joy/Stack'; +import LinearProgress from '@mui/joy/LinearProgress'; + +export default function LinearProgressColors() { + return ( + + + + + + ); +} diff --git a/docs/data/joy/components/linear-progress/LinearProgressSizes.tsx.preview b/docs/data/joy/components/linear-progress/LinearProgressSizes.tsx.preview new file mode 100644 index 00000000000000..c84d628b3c62d5 --- /dev/null +++ b/docs/data/joy/components/linear-progress/LinearProgressSizes.tsx.preview @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/data/joy/components/linear-progress/LinearProgressThickness.js b/docs/data/joy/components/linear-progress/LinearProgressThickness.js new file mode 100644 index 00000000000000..ca65d51b5b3ab3 --- /dev/null +++ b/docs/data/joy/components/linear-progress/LinearProgressThickness.js @@ -0,0 +1,6 @@ +import * as React from 'react'; +import LinearProgress from '@mui/joy/LinearProgress'; + +export default function LinearProgressThickness() { + return ; +} diff --git a/docs/data/joy/components/linear-progress/LinearProgressThickness.tsx b/docs/data/joy/components/linear-progress/LinearProgressThickness.tsx new file mode 100644 index 00000000000000..ca65d51b5b3ab3 --- /dev/null +++ b/docs/data/joy/components/linear-progress/LinearProgressThickness.tsx @@ -0,0 +1,6 @@ +import * as React from 'react'; +import LinearProgress from '@mui/joy/LinearProgress'; + +export default function LinearProgressThickness() { + return ; +} diff --git a/docs/data/joy/components/linear-progress/LinearProgressThickness.tsx.preview b/docs/data/joy/components/linear-progress/LinearProgressThickness.tsx.preview new file mode 100644 index 00000000000000..7899cbd4182d0a --- /dev/null +++ b/docs/data/joy/components/linear-progress/LinearProgressThickness.tsx.preview @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/data/joy/components/linear-progress/LinearProgressUsage.js b/docs/data/joy/components/linear-progress/LinearProgressUsage.js new file mode 100644 index 00000000000000..2a3718318cc03f --- /dev/null +++ b/docs/data/joy/components/linear-progress/LinearProgressUsage.js @@ -0,0 +1,46 @@ +import * as React from 'react'; +import JoyUsageDemo from 'docs/src/modules/components/JoyUsageDemo'; +import LinearProgress from '@mui/joy/LinearProgress'; +import Box from '@mui/joy/Box'; + +export default function LinearProgressUsage() { + return ( + ( + + + + )} + /> + ); +} diff --git a/docs/data/joy/components/linear-progress/LinearProgressVariables.js b/docs/data/joy/components/linear-progress/LinearProgressVariables.js new file mode 100644 index 00000000000000..990eb32516949c --- /dev/null +++ b/docs/data/joy/components/linear-progress/LinearProgressVariables.js @@ -0,0 +1,35 @@ +import * as React from 'react'; +import JoyVariablesDemo from 'docs/src/modules/components/JoyVariablesDemo'; +import LinearProgress from '@mui/joy/LinearProgress'; +import Box from '@mui/joy/Box'; + +export default function LinearProgressVariables() { + return ( + ( + + + + )} + /> + ); +} diff --git a/docs/data/joy/components/linear-progress/LinearProgressVariables.tsx b/docs/data/joy/components/linear-progress/LinearProgressVariables.tsx new file mode 100644 index 00000000000000..990eb32516949c --- /dev/null +++ b/docs/data/joy/components/linear-progress/LinearProgressVariables.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; +import JoyVariablesDemo from 'docs/src/modules/components/JoyVariablesDemo'; +import LinearProgress from '@mui/joy/LinearProgress'; +import Box from '@mui/joy/Box'; + +export default function LinearProgressVariables() { + return ( + ( + + + + )} + /> + ); +} diff --git a/docs/data/joy/components/linear-progress/LinearProgressVariants.js b/docs/data/joy/components/linear-progress/LinearProgressVariants.js new file mode 100644 index 00000000000000..58e9d0880db9da --- /dev/null +++ b/docs/data/joy/components/linear-progress/LinearProgressVariants.js @@ -0,0 +1,14 @@ +import * as React from 'react'; +import Stack from '@mui/joy/Stack'; +import LinearProgress from '@mui/joy/LinearProgress'; + +export default function LinearProgressVariants() { + return ( + + + + + + + ); +} diff --git a/docs/data/joy/components/linear-progress/LinearProgressVariants.tsx b/docs/data/joy/components/linear-progress/LinearProgressVariants.tsx new file mode 100644 index 00000000000000..58e9d0880db9da --- /dev/null +++ b/docs/data/joy/components/linear-progress/LinearProgressVariants.tsx @@ -0,0 +1,14 @@ +import * as React from 'react'; +import Stack from '@mui/joy/Stack'; +import LinearProgress from '@mui/joy/LinearProgress'; + +export default function LinearProgressVariants() { + return ( + + + + + + + ); +} diff --git a/docs/data/joy/components/linear-progress/LinearProgressVariants.tsx.preview b/docs/data/joy/components/linear-progress/LinearProgressVariants.tsx.preview new file mode 100644 index 00000000000000..caa85179e48077 --- /dev/null +++ b/docs/data/joy/components/linear-progress/LinearProgressVariants.tsx.preview @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/data/joy/components/linear-progress/LinearProgressWithLabel.js b/docs/data/joy/components/linear-progress/LinearProgressWithLabel.js new file mode 100644 index 00000000000000..1c7a2d94e0f40d --- /dev/null +++ b/docs/data/joy/components/linear-progress/LinearProgressWithLabel.js @@ -0,0 +1,43 @@ +import * as React from 'react'; +import LinearProgress from '@mui/joy/LinearProgress'; +import Typography from '@mui/joy/Typography'; + +export default function LinearProgressWithLabel() { + const [progress, setProgress] = React.useState(0); + + React.useEffect(() => { + const timer = setInterval(() => { + setProgress((prevProgress) => (prevProgress >= 100 ? 0 : prevProgress + 10)); + }, 800); + + return () => { + clearInterval(timer); + }; + }, []); + + return ( + + + LOADING… {`${Math.round(progress)}%`} + + + ); +} diff --git a/docs/data/joy/components/linear-progress/LinearProgressWithLabel.tsx b/docs/data/joy/components/linear-progress/LinearProgressWithLabel.tsx new file mode 100644 index 00000000000000..1c7a2d94e0f40d --- /dev/null +++ b/docs/data/joy/components/linear-progress/LinearProgressWithLabel.tsx @@ -0,0 +1,43 @@ +import * as React from 'react'; +import LinearProgress from '@mui/joy/LinearProgress'; +import Typography from '@mui/joy/Typography'; + +export default function LinearProgressWithLabel() { + const [progress, setProgress] = React.useState(0); + + React.useEffect(() => { + const timer = setInterval(() => { + setProgress((prevProgress) => (prevProgress >= 100 ? 0 : prevProgress + 10)); + }, 800); + + return () => { + clearInterval(timer); + }; + }, []); + + return ( + + + LOADING… {`${Math.round(progress)}%`} + + + ); +} diff --git a/docs/data/joy/components/linear-progress/linear-progress.md b/docs/data/joy/components/linear-progress/linear-progress.md new file mode 100644 index 00000000000000..28584de3d7d3b5 --- /dev/null +++ b/docs/data/joy/components/linear-progress/linear-progress.md @@ -0,0 +1,83 @@ +--- +product: joy-ui +title: React Linear Progress component +githubLabel: 'component: LinearProgress' +--- + +# Linear Progress + +

Linear Progress indicators, commonly known as loaders, express an unspecified wait time or display the length of a process.

+ +## Introduction + +Progress indicators inform users about the status of ongoing processes, such as loading an app, submitting a form, or saving updates. + +The `LinearProgress` is indeterminate by default, indicating an unspecified wait time. +To actually have it represent how long an operation will take, use the [determinate](#determinate) mode. + +The animations of the components rely on CSS as much as possible to work even before the JavaScript is loaded. + +{{"demo": "LinearProgressUsage.js", "hideToolbar": true}} + +{{"component": "modules/components/ComponentLinkHeader.js", "design": false}} + +## Component + +After [installation](/joy-ui/getting-started/installation/), you can start building with this component using the following basic elements: + +```jsx +import LinearProgress from '@mui/joy/LinearProgress'; + +export default function MyApp() { + return ; +} +``` + +### Variants + +The linear progress component supports the four global variants: `solid`, `soft` (default), `outlined`, and `plain`. + +{{"demo": "LinearProgressVariants.js"}} + +### Colors + +Every palette included in the theme is available via the `color` prop. +Play around combining different colors. + +{{"demo": "LinearProgressColors.js"}} + +### Sizes + +The linear progress component comes with three sizes out of the box: `sm`, `md` (the default), and `lg`. + +{{"demo": "LinearProgressSizes.js"}} + +:::success +To learn how to add more sizes to the component, check out [Themed components—Extend sizes](/joy-ui/customization/themed-components/#extend-sizes). +::: + +### Determinate + +You can use the `determinate` prop if you want to indicate a specified wait time. + +{{"demo": "LinearProgressDeterminate.js"}} + +### Thickness + +Provides a number to `thickness` prop to control the bar's stroke width. + +{{"demo": "LinearProgressThickness.js"}} + +## CSS variables + +Play around with all the CSS variables available on the component to see how the design changes. + +You can use those to customize the component on both the `sx` prop and the theme. + +{{"demo": "LinearProgressVariables.js", "hideToolbar": true}} + +## Common examples + +### With label + +{{"demo": "LinearProgressWithLabel.js"}} diff --git a/docs/data/joy/pages.ts b/docs/data/joy/pages.ts index ebca4c87166282..4b5411897787a7 100644 --- a/docs/data/joy/pages.ts +++ b/docs/data/joy/pages.ts @@ -59,6 +59,7 @@ const pages = [ children: [ { pathname: '/joy-ui/react-alert' }, { pathname: '/joy-ui/react-circular-progress' }, + { pathname: '/joy-ui/react-linear-progress' }, { pathname: '/joy-ui/react-modal' }, ], }, diff --git a/docs/pages/joy-ui/react-linear-progress.js b/docs/pages/joy-ui/react-linear-progress.js new file mode 100644 index 00000000000000..06fd75c48446ff --- /dev/null +++ b/docs/pages/joy-ui/react-linear-progress.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docs/data/joy/components/linear-progress/linear-progress.md?@mui/markdown'; + +export default function Page() { + return ; +} diff --git a/packages/mui-joy/src/LinearProgress/LinearProgress.test.tsx b/packages/mui-joy/src/LinearProgress/LinearProgress.test.tsx new file mode 100644 index 00000000000000..f5c135e0ecb978 --- /dev/null +++ b/packages/mui-joy/src/LinearProgress/LinearProgress.test.tsx @@ -0,0 +1,74 @@ +import * as React from 'react'; +import { expect } from 'chai'; +import { createRenderer, describeConformance } from 'test/utils'; +import { ThemeProvider } from '@mui/joy/styles'; +import LinearProgress, { linearProgressClasses as classes } from '@mui/joy/LinearProgress'; +import { unstable_capitalize as capitalize } from '@mui/utils'; + +describe('', () => { + const { render } = createRenderer(); + describeConformance(, () => ({ + classes, + inheritComponent: 'div', + render, + ThemeProvider, + muiName: 'JoyLinearProgress', + refInstanceof: window.HTMLDivElement, + testVariantProps: { determinate: true }, + testCustomVariant: true, + skip: ['classesRoot', 'componentsProp'], + })); + + describe('prop: determinate', () => { + it('should render a determinate circular progress', () => { + const { getByRole } = render(); + + expect(getByRole('progressbar')).to.have.class(classes.determinate); + }); + }); + + describe('prop: variant', () => { + it('soft by default', () => { + const { getByRole } = render(); + expect(getByRole('progressbar')).to.have.class(classes.variantSoft); + }); + (['plain', 'outlined', 'soft', 'solid'] as const).forEach((variant) => { + it(`should render ${variant}`, () => { + const { getByRole } = render(); + expect(getByRole('progressbar')).to.have.class( + classes[`variant${capitalize(variant)}` as keyof typeof classes], + ); + }); + }); + }); + + describe('prop: color', () => { + it('adds a primary class by default', () => { + const { getByRole } = render(); + expect(getByRole('progressbar')).to.have.class(classes.colorPrimary); + }); + (['primary', 'success', 'info', 'danger', 'neutral', 'warning'] as const).forEach((color) => { + it(`should render ${color}`, () => { + const { getByRole } = render(); + expect(getByRole('progressbar')).to.have.class( + classes[`color${capitalize(color)}` as keyof typeof classes], + ); + }); + }); + }); + + describe('prop: size', () => { + it('md by default', () => { + const { getByRole } = render(); + expect(getByRole('progressbar')).to.have.class(classes.sizeMd); + }); + (['sm', 'md', 'lg'] as const).forEach((size) => { + it(`should render ${size}`, () => { + const { getByRole } = render(); + expect(getByRole('progressbar')).to.have.class( + classes[`size${capitalize(size)}` as keyof typeof classes], + ); + }); + }); + }); +}); diff --git a/packages/mui-joy/src/LinearProgress/LinearProgress.tsx b/packages/mui-joy/src/LinearProgress/LinearProgress.tsx new file mode 100644 index 00000000000000..97d07b66a10601 --- /dev/null +++ b/packages/mui-joy/src/LinearProgress/LinearProgress.tsx @@ -0,0 +1,266 @@ +import { unstable_composeClasses as composeClasses } from '@mui/base'; +import { useSlotProps } from '@mui/base/utils'; +import { css, keyframes } from '@mui/system'; +import { OverridableComponent } from '@mui/types'; +import { unstable_capitalize as capitalize } from '@mui/utils'; +import clsx from 'clsx'; +import PropTypes from 'prop-types'; +import * as React from 'react'; +import styled from '../styles/styled'; +import useThemeProps from '../styles/useThemeProps'; +import { getLinearProgressUtilityClass } from './linearProgressClasses'; +import { + LinearProgressOwnerState, + LinearProgressProps, + LinearProgressTypeMap, +} from './LinearProgressProps'; + +// TODO: replace `left` with `inset-inline-start` in the future to work with writing-mode. https://caniuse.com/?search=inset-inline-start +// replace `width` with `inline-size`, not sure why inline-size does not work with animation in Safari. +const progressKeyframe = keyframes` + 0% { + left: var(--_LinearProgress-progressInset); + width: var(--LinearProgress-progressMinWidth); + } + + 25% { + width: var(--LinearProgress-progressMaxWidth); + } + + 50% { + left: var(--_LinearProgress-progressLeft); + width: var(--LinearProgress-progressMinWidth); + } + + 75% { + width: var(--LinearProgress-progressMaxWidth); + } + + 100% { + left: var(--_LinearProgress-progressInset); + width: var(--LinearProgress-progressMinWidth); + } +`; + +const useUtilityClasses = (ownerState: LinearProgressOwnerState) => { + const { determinate, color, variant, size } = ownerState; + + const slots = { + root: [ + 'root', + determinate && 'determinate', + color && `color${capitalize(color)}`, + variant && `variant${capitalize(variant)}`, + size && `size${capitalize(size)}`, + ], + progress: ['progress'], + }; + + return composeClasses(slots, getLinearProgressUtilityClass, {}); +}; + +const LinearProgressRoot = styled('div', { + name: 'JoyLinearProgress', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})<{ ownerState: LinearProgressOwnerState }>( + ({ ownerState, theme }) => ({ + // public variables + '--LinearProgress-radius': 'var(--LinearProgress-thickness)', + '--LinearProgress-progressThickness': 'var(--LinearProgress-thickness)', + '--LinearProgress-progressRadius': + 'max(var(--LinearProgress-radius) - var(--_LinearProgress-padding), min(var(--_LinearProgress-padding) / 2, var(--LinearProgress-radius) / 2))', + ...(ownerState.size === 'sm' && { + '--LinearProgress-thickness': '4px', + }), + ...(ownerState.size === 'md' && { + '--LinearProgress-thickness': '6px', + }), + ...(ownerState.size === 'lg' && { + '--LinearProgress-thickness': '8px', + }), + ...(ownerState.thickness && { + '--LinearProgress-thickness': `${ownerState.thickness}px`, + }), + ...(!ownerState.determinate && { + '--LinearProgress-progressMinWidth': 'calc(var(--LinearProgress-percent) * 1% / 2)', + '--LinearProgress-progressMaxWidth': 'calc(var(--LinearProgress-percent) * 1%)', + '--_LinearProgress-progressLeft': + 'calc(100% - var(--LinearProgress-progressMinWidth) - var(--_LinearProgress-progressInset))', + '--_LinearProgress-progressInset': + 'calc(var(--LinearProgress-thickness) / 2 - var(--LinearProgress-progressThickness) / 2)', + }), + minBlockSize: 'var(--LinearProgress-thickness)', + boxSizing: 'border-box', + borderRadius: 'var(--LinearProgress-radius)', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + flex: 1, + padding: 'var(--_LinearProgress-padding)', + position: 'relative', + ...theme.variants[ownerState.variant!]?.[ownerState.color!], + '--_LinearProgress-padding': + 'max((var(--LinearProgress-thickness) - 2 * var(--variant-borderWidth) - var(--LinearProgress-progressThickness)) / 2, 0px)', + '&::before': { + content: '""', + display: 'block', + boxSizing: 'inherit', + blockSize: 'var(--LinearProgress-progressThickness)', + borderRadius: 'var(--LinearProgress-progressRadius)', + backgroundColor: 'currentColor', + color: 'inherit', + position: 'absolute', // required to make `left` animation works. + }, + }), + ({ ownerState }) => + ownerState.determinate + ? { + '&::before': { + left: 'var(--_LinearProgress-padding)', + transition: 'inline-size 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', + inlineSize: + 'calc(var(--LinearProgress-percent) * 1% - 2 * var(--_LinearProgress-padding))', + }, + } + : css` + &::before { + animation: ${progressKeyframe} + var(--LinearProgress-circulation, 2.5s ease-in-out 0s infinite normal none running); + } + `, +); + +/** + * ## ARIA + * + * If the progress bar is describing the loading progress of a particular region of a page, + * you should use `aria-describedby` to point to the progress bar, and set the `aria-busy` + * attribute to `true` on that region until it has finished loading. + */ +const LinearProgress = React.forwardRef(function LinearProgress(inProps, ref) { + const props = useThemeProps({ + props: inProps, + name: 'JoyLinearProgress', + }); + + const { + component = 'div', + children, + className, + color = 'primary', + size = 'md', + variant = 'soft', + thickness, + determinate = false, + value = determinate ? 0 : 25, // `25` is the 1/4 of the bar. + ...other + } = props; + + const ownerState = { + ...props, + color, + size, + variant, + thickness, + value, + determinate, + instanceSize: inProps.size, + }; + + const classes = useUtilityClasses(ownerState); + + const rootProps = useSlotProps({ + elementType: LinearProgressRoot, + externalSlotProps: {}, + externalForwardedProps: other, + ownerState, + additionalProps: { + ref, + as: component, + role: 'progressbar', + style: { + '--LinearProgress-percent': value, + }, + }, + className: clsx(classes.root, className), + ...(typeof value === 'number' && + determinate && { + 'aria-valuenow': Math.round(value), + }), + }); + + return {children}; +}) as OverridableComponent; + +LinearProgress.propTypes /* remove-proptypes */ = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + /** + * @ignore + */ + children: PropTypes.node, + /** + * @ignore + */ + className: PropTypes.string, + /** + * The color of the component. It supports those theme colors that make sense for this component. + * @default 'primary' + */ + color: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([ + PropTypes.oneOf(['danger', 'info', 'neutral', 'primary', 'success', 'warning']), + PropTypes.string, + ]), + /** + * The component used for the root node. + * Either a string to use a HTML element or a component. + */ + component: PropTypes.elementType, + /** + * The boolean to select a variant. + * Use indeterminate when there is no progress value. + * @default false + */ + determinate: PropTypes.bool, + /** + * The size of the component. + * It accepts theme values between 'sm' and 'lg'. + * @default 'md' + */ + size: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([ + PropTypes.oneOf(['sm', 'md', 'lg']), + PropTypes.string, + ]), + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), + /** + * The thickness of the bar. + */ + thickness: PropTypes.number, + /** + * The value of the progress indicator for the determinate variant. + * Value between 0 and 100. + * + * For indeterminate, @default 25 + */ + value: PropTypes.number, + /** + * The variant to use. + * @default 'soft' + */ + variant: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([ + PropTypes.oneOf(['outlined', 'plain', 'soft', 'solid']), + PropTypes.string, + ]), +} as any; + +export default LinearProgress; diff --git a/packages/mui-joy/src/LinearProgress/LinearProgressProps.ts b/packages/mui-joy/src/LinearProgress/LinearProgressProps.ts new file mode 100644 index 00000000000000..663057eafa097a --- /dev/null +++ b/packages/mui-joy/src/LinearProgress/LinearProgressProps.ts @@ -0,0 +1,64 @@ +import * as React from 'react'; +import { OverridableStringUnion, OverrideProps } from '@mui/types'; +import { ColorPaletteProp, SxProps, VariantProp } from '../styles/types'; + +export type LinearProgressSlot = 'root'; + +export interface LinearProgressPropsColorOverrides {} +export interface LinearProgressPropsSizeOverrides {} +export interface LinearProgressPropsVariantOverrides {} + +export interface LinearProgressTypeMap

{ + props: P & { + /** + * The color of the component. It supports those theme colors that make sense for this component. + * @default 'primary' + */ + color?: OverridableStringUnion; + /** + * The boolean to select a variant. + * Use indeterminate when there is no progress value. + * @default false + */ + determinate?: true | false; + /** + * The size of the component. + * It accepts theme values between 'sm' and 'lg'. + * @default 'md' + */ + size?: OverridableStringUnion<'sm' | 'md' | 'lg', LinearProgressPropsSizeOverrides>; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps; + /** + * The thickness of the bar. + */ + thickness?: number; + /** + * The value of the progress indicator for the determinate variant. + * Value between 0 and 100. + * + * For indeterminate, @default 25 + */ + value?: number; + /** + * The variant to use. + * @default 'soft' + */ + variant?: OverridableStringUnion; + }; + defaultComponent: D; +} + +export type LinearProgressProps< + D extends React.ElementType = LinearProgressTypeMap['defaultComponent'], + P = { component?: React.ElementType }, +> = OverrideProps, D>; + +export interface LinearProgressOwnerState extends LinearProgressProps { + /** + * @internal the explicit size on the instance: + */ + instanceSize: LinearProgressProps['size']; +} diff --git a/packages/mui-joy/src/LinearProgress/index.ts b/packages/mui-joy/src/LinearProgress/index.ts new file mode 100644 index 00000000000000..4ed393247c7524 --- /dev/null +++ b/packages/mui-joy/src/LinearProgress/index.ts @@ -0,0 +1,4 @@ +export { default } from './LinearProgress'; +export * from './linearProgressClasses'; +export { default as linearProgressClasses } from './linearProgressClasses'; +export * from './LinearProgressProps'; diff --git a/packages/mui-joy/src/LinearProgress/linearProgressClasses.ts b/packages/mui-joy/src/LinearProgress/linearProgressClasses.ts new file mode 100644 index 00000000000000..6de23a336cd3ba --- /dev/null +++ b/packages/mui-joy/src/LinearProgress/linearProgressClasses.ts @@ -0,0 +1,63 @@ +import { generateUtilityClass, generateUtilityClasses } from '../className'; + +export interface LinearProgressClasses { + /** Styles applied to the root element. */ + root: string; + /** Styles applied to the root element if `determinate` is true. */ + determinate: string; + /** Styles applied to the `progress` element. */ + progress: string; + /** Styles applied to the root element if `color="primary"`. */ + colorPrimary: string; + /** Styles applied to the root element if `color="neutral"`. */ + colorNeutral: string; + /** Styles applied to the root element if `color="danger"`. */ + colorDanger: string; + /** Styles applied to the root element if `color="info"`. */ + colorInfo: string; + /** Styles applied to the root element if `color="success"`. */ + colorSuccess: string; + /** Styles applied to the root element if `color="warning"`. */ + colorWarning: string; + /** Styles applied to the root element if `size="sm"`. */ + sizeSm: string; + /** Styles applied to the root element if `size="md"`. */ + sizeMd: string; + /** Styles applied to the root element if `size="lg"`. */ + sizeLg: string; + /** Styles applied to the root element if `variant="plain"`. */ + variantPlain: string; + /** Styles applied to the root element if `variant="outlined"`. */ + variantOutlined: string; + /** Styles applied to the root element if `variant="soft"`. */ + variantSoft: string; + /** Styles applied to the root element if `variant="solid"`. */ + variantSolid: string; +} + +export type LinearProgressClassKey = keyof LinearProgressClasses; + +export function getLinearProgressUtilityClass(slot: string): string { + return generateUtilityClass('JoyLinearProgress', slot); +} + +const linearProgressClasses: LinearProgressClasses = generateUtilityClasses('JoyLinearProgress', [ + 'root', + 'determinate', + 'progress', + 'colorPrimary', + 'colorNeutral', + 'colorDanger', + 'colorInfo', + 'colorSuccess', + 'colorWarning', + 'sizeSm', + 'sizeMd', + 'sizeLg', + 'variantPlain', + 'variantOutlined', + 'variantSoft', + 'variantSolid', +]); + +export default linearProgressClasses; diff --git a/packages/mui-joy/src/styles/components.d.ts b/packages/mui-joy/src/styles/components.d.ts index 5905c9e18ec9db..40d34f4436e2bc 100644 --- a/packages/mui-joy/src/styles/components.d.ts +++ b/packages/mui-joy/src/styles/components.d.ts @@ -69,6 +69,11 @@ import { IconButtonSlot, } from '../IconButton/IconButtonProps'; import { InputProps, InputOwnerState, InputSlot } from '../Input/InputProps'; +import { + LinearProgressProps, + LinearProgressOwnerState, + LinearProgressSlot, +} from '../LinearProgress/LinearProgressProps'; import { LinkProps, LinkOwnerState, LinkSlot } from '../Link/LinkProps'; import { ListProps, ListOwnerState, ListSlot } from '../List/ListProps'; import { @@ -257,6 +262,10 @@ export interface Components { defaultProps?: Partial; styleOverrides?: OverridesStyleRules; }; + JoyLinearProgress?: { + defaultProps?: Partial; + styleOverrides?: OverridesStyleRules; + }; JoyLink?: { defaultProps?: Partial; styleOverrides?: OverridesStyleRules; diff --git a/packages/mui-joy/src/styles/extendTheme.spec.ts b/packages/mui-joy/src/styles/extendTheme.spec.ts index b09fdf8ebef8a7..42b674029b2180 100644 --- a/packages/mui-joy/src/styles/extendTheme.spec.ts +++ b/packages/mui-joy/src/styles/extendTheme.spec.ts @@ -23,6 +23,7 @@ import { GridProps } from '@mui/joy/Grid'; import { IconButtonOwnerState } from '@mui/joy/IconButton'; import { InputOwnerState } from '@mui/joy/Input'; import { LinkOwnerState } from '@mui/joy/Link'; +import { LinearProgressOwnerState } from '@mui/joy/LinearProgress'; import { ListOwnerState } from '@mui/joy/List'; import { ListDividerOwnerState } from '@mui/joy/ListDivider'; import { ListSubheaderOwnerState } from '@mui/joy/ListSubheader'; @@ -314,8 +315,9 @@ extendTheme({ }, JoyCircularProgress: { defaultProps: { - variant: 'solid', + variant: 'soft', color: 'primary', + size: 'md', }, styleOverrides: { root: ({ ownerState }) => { @@ -475,6 +477,21 @@ extendTheme({ }, }, }, + JoyLinearProgress: { + defaultProps: { + variant: 'soft', + color: 'primary', + size: 'md', + }, + styleOverrides: { + root: ({ ownerState }) => { + expectType, typeof ownerState>( + ownerState, + ); + return {}; + }, + }, + }, JoyList: { defaultProps: { size: 'sm',