From 415d551f5d2401c93ef718e5d5e02d68a530726c Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Fri, 28 Jun 2024 11:57:36 +0800 Subject: [PATCH] Add Progress hooks and components --- .../progress/UnstyledProgressIntroduction.js | 85 ++++++ .../progress/UnstyledProgressIntroduction.tsx | 85 ++++++ .../UnstyledProgressIntroduction.tsx.preview | 9 + .../data/base/components/progress/progress.md | 139 ++++++++++ docs/data/base/pages.ts | 2 +- docs/data/base/pagesApi.js | 28 ++ docs/pages/base-ui/api/progress-buffer.json | 16 ++ .../pages/base-ui/api/progress-indicator.json | 16 ++ docs/pages/base-ui/api/progress-root.json | 41 +++ docs/pages/base-ui/api/progress-track.json | 16 ++ .../base-ui/api/use-progress-buffer.json | 24 ++ .../base-ui/api/use-progress-indicator.json | 28 ++ docs/pages/base-ui/api/use-progress-root.json | 59 ++++ .../base-ui/react-progress/[docsTab]/index.js | 106 +++++++ docs/pages/base-ui/react-progress/index.js | 13 + docs/pages/experiments/progress.tsx | 261 ++++++++++++++++++ .../progress-buffer/progress-buffer.json | 10 + .../progress-indicator.json | 10 + .../api-docs/progress-root/progress-root.json | 32 +++ .../progress-track/progress-track.json | 10 + .../use-progress-buffer.json | 10 + .../use-progress-indicator.json | 12 + .../use-progress-root/use-progress-root.json | 32 +++ .../src/Progress/Buffer/ProgressBuffer.tsx | 61 ++++ .../Progress/Buffer/ProgressBuffer.types.ts | 32 +++ .../src/Progress/Buffer/useProgressBuffer.ts | 55 ++++ .../Progress/Indicator/ProgressIndicator.tsx | 57 ++++ .../Indicator/ProgressIndicator.types.ts | 34 +++ .../Indicator/useProgressIndicator.ts | 56 ++++ .../src/Progress/Root/ProgressContext.tsx | 25 ++ .../src/Progress/Root/ProgressRoot.tsx | 147 ++++++++++ .../src/Progress/Root/ProgressRoot.types.ts | 97 +++++++ .../mui-base/src/Progress/Root/styleHooks.ts | 8 + .../src/Progress/Root/useProgressRoot.ts | 84 ++++++ .../src/Progress/Track/ProgressTrack.tsx | 48 ++++ .../src/Progress/Track/ProgressTrack.types.ts | 4 + .../mui-base/src/Progress/index.barrel.ts | 23 ++ packages/mui-base/src/Progress/index.ts | 29 ++ packages/mui-base/src/index.ts | 1 + 39 files changed, 1804 insertions(+), 1 deletion(-) create mode 100644 docs/data/base/components/progress/UnstyledProgressIntroduction.js create mode 100644 docs/data/base/components/progress/UnstyledProgressIntroduction.tsx create mode 100644 docs/data/base/components/progress/UnstyledProgressIntroduction.tsx.preview create mode 100644 docs/data/base/components/progress/progress.md create mode 100644 docs/pages/base-ui/api/progress-buffer.json create mode 100644 docs/pages/base-ui/api/progress-indicator.json create mode 100644 docs/pages/base-ui/api/progress-root.json create mode 100644 docs/pages/base-ui/api/progress-track.json create mode 100644 docs/pages/base-ui/api/use-progress-buffer.json create mode 100644 docs/pages/base-ui/api/use-progress-indicator.json create mode 100644 docs/pages/base-ui/api/use-progress-root.json create mode 100644 docs/pages/base-ui/react-progress/[docsTab]/index.js create mode 100644 docs/pages/base-ui/react-progress/index.js create mode 100644 docs/pages/experiments/progress.tsx create mode 100644 docs/translations/api-docs/progress-buffer/progress-buffer.json create mode 100644 docs/translations/api-docs/progress-indicator/progress-indicator.json create mode 100644 docs/translations/api-docs/progress-root/progress-root.json create mode 100644 docs/translations/api-docs/progress-track/progress-track.json create mode 100644 docs/translations/api-docs/use-progress-buffer/use-progress-buffer.json create mode 100644 docs/translations/api-docs/use-progress-indicator/use-progress-indicator.json create mode 100644 docs/translations/api-docs/use-progress-root/use-progress-root.json create mode 100644 packages/mui-base/src/Progress/Buffer/ProgressBuffer.tsx create mode 100644 packages/mui-base/src/Progress/Buffer/ProgressBuffer.types.ts create mode 100644 packages/mui-base/src/Progress/Buffer/useProgressBuffer.ts create mode 100644 packages/mui-base/src/Progress/Indicator/ProgressIndicator.tsx create mode 100644 packages/mui-base/src/Progress/Indicator/ProgressIndicator.types.ts create mode 100644 packages/mui-base/src/Progress/Indicator/useProgressIndicator.ts create mode 100644 packages/mui-base/src/Progress/Root/ProgressContext.tsx create mode 100644 packages/mui-base/src/Progress/Root/ProgressRoot.tsx create mode 100644 packages/mui-base/src/Progress/Root/ProgressRoot.types.ts create mode 100644 packages/mui-base/src/Progress/Root/styleHooks.ts create mode 100644 packages/mui-base/src/Progress/Root/useProgressRoot.ts create mode 100644 packages/mui-base/src/Progress/Track/ProgressTrack.tsx create mode 100644 packages/mui-base/src/Progress/Track/ProgressTrack.types.ts create mode 100644 packages/mui-base/src/Progress/index.barrel.ts create mode 100644 packages/mui-base/src/Progress/index.ts diff --git a/docs/data/base/components/progress/UnstyledProgressIntroduction.js b/docs/data/base/components/progress/UnstyledProgressIntroduction.js new file mode 100644 index 0000000000..24877d48c0 --- /dev/null +++ b/docs/data/base/components/progress/UnstyledProgressIntroduction.js @@ -0,0 +1,85 @@ +import * as React from 'react'; +import { useTheme } from '@mui/system'; +import * as Progress from '@base_ui/react/Progress'; + +export default function UnstyledProgressIntroduction() { + return ( +
+ + + Uploading files + + + + + + +
+ ); +} + +function useIsDarkMode() { + const theme = useTheme(); + return theme.palette.mode === 'dark'; +} + +export function Styles() { + const isDarkMode = useIsDarkMode(); + return ( + + ); +} + +const grey = { + 50: '#F3F6F9', + 100: '#E5EAF2', + 200: '#DAE2ED', + 300: '#C7D0DD', + 400: '#B0B8C4', + 500: '#9DA8B7', + 600: '#6B7A90', + 700: '#434D5B', + 800: '#303740', + 900: '#1C2025', +}; + +const BLUE400 = '#3399FF'; +const BLUE500 = '#007FFF'; diff --git a/docs/data/base/components/progress/UnstyledProgressIntroduction.tsx b/docs/data/base/components/progress/UnstyledProgressIntroduction.tsx new file mode 100644 index 0000000000..24877d48c0 --- /dev/null +++ b/docs/data/base/components/progress/UnstyledProgressIntroduction.tsx @@ -0,0 +1,85 @@ +import * as React from 'react'; +import { useTheme } from '@mui/system'; +import * as Progress from '@base_ui/react/Progress'; + +export default function UnstyledProgressIntroduction() { + return ( +
+ + + Uploading files + + + + + + +
+ ); +} + +function useIsDarkMode() { + const theme = useTheme(); + return theme.palette.mode === 'dark'; +} + +export function Styles() { + const isDarkMode = useIsDarkMode(); + return ( + + ); +} + +const grey = { + 50: '#F3F6F9', + 100: '#E5EAF2', + 200: '#DAE2ED', + 300: '#C7D0DD', + 400: '#B0B8C4', + 500: '#9DA8B7', + 600: '#6B7A90', + 700: '#434D5B', + 800: '#303740', + 900: '#1C2025', +}; + +const BLUE400 = '#3399FF'; +const BLUE500 = '#007FFF'; diff --git a/docs/data/base/components/progress/UnstyledProgressIntroduction.tsx.preview b/docs/data/base/components/progress/UnstyledProgressIntroduction.tsx.preview new file mode 100644 index 0000000000..b710e80892 --- /dev/null +++ b/docs/data/base/components/progress/UnstyledProgressIntroduction.tsx.preview @@ -0,0 +1,9 @@ + + + Uploading files + + + + + + \ No newline at end of file diff --git a/docs/data/base/components/progress/progress.md b/docs/data/base/components/progress/progress.md new file mode 100644 index 0000000000..f90e093b0c --- /dev/null +++ b/docs/data/base/components/progress/progress.md @@ -0,0 +1,139 @@ +--- +productId: base-ui +title: React Progress components +components: ProgressRoot, ProgressTrack, ProgressIndicator, ProgressBuffer +hooks: useProgressRoot, useProgressIndicator, useProgressBuffer +githubLabel: 'component: progress' +waiAria: https://www.w3.org/TR/wai-aria-1.2/#progressbar +packageName: '@base_ui/react' +--- + +# Progress + +

The Progress component displays the status of a task or operation over time.

+ +{{"component": "@mui/docs/ComponentLinkHeader", "design": false}} + +{{"component": "modules/components/ComponentPageTabs.js"}} + +{{"demo": "UnstyledProgressIntroduction.js", "defaultCodeOpen": false, "bg": "gradient"}} + +## Installation + +Base UI components are all available as a single package. + + + +```bash npm +npm install @base_ui/react +``` + +```bash yarn +yarn add @base_ui/react +``` + +```bash pnpm +pnpm add @base_ui/react +``` + + + +Once you have the package installed, import the component. + +```jsx +import * as Progress from '@base_ui/react/Progress'; +``` + +### Anatomy + +Progress + +- `` is a top-level component that wraps the other components. +- `` renders the rail that represents the total length or duration of progress. +- `` renders the filled portion of the track. +- `` renders an optional buffer indicator that represents the progress of a loading buffer. + +```tsx + + + + + + +``` + +## Value + +### Determinate + +The `value` prop represents the percentage value of the Progress component. The default minimum and maximum values are `0` and `100`, and can be changed with the `min` and `max` props. + +```tsx +function App() { + const [progressValue] = React.useState(25); + return ( + + + + + + ); +} +``` + +### Indeterminate + +Set `value` to `null` to configure an indeterminate progress bar. + +```tsx + + + + + +``` + + + +## RTL + +Set the `direction` prop to `'rtl'` to change the direction that the `Indicator` fills towards for right-to-left languages: + +```jsx +{/* Subcomponents */} +``` + + + +## Overriding default components + +Use the `render` prop to override the rendered element for all subcomponents: + +```jsx +} /> +// or + } /> +``` + +## Accessibility + +The Progress component implements the [ARIA progressbar specification](https://www.w3.org/TR/wai-aria-1.2/#progressbar). + +When using Progress, ensure that it has a human-readable text label by using either the `aria-label`, `aria-labelledby`, or `getAriaLabel` prop: + +```tsx + + Loading progress + + + + + +// or + + + + + + +``` diff --git a/docs/data/base/pages.ts b/docs/data/base/pages.ts index 0ce753a28b..63c7f332e9 100644 --- a/docs/data/base/pages.ts +++ b/docs/data/base/pages.ts @@ -49,7 +49,7 @@ const pages: readonly MuiPage[] = [ children: [ { pathname: '/base-ui/react-alert-dialog', title: 'Alert Dialog' }, { pathname: '/base-ui/react-dialog', title: 'Dialog' }, - // { pathname: '/base-ui/react-snackbar', title: 'Snackbar' }, + { pathname: '/base-ui/react-progress', title: 'Progress' }, ], }, // { diff --git a/docs/data/base/pagesApi.js b/docs/data/base/pagesApi.js index d9d16fe2e6..e38fa86a78 100644 --- a/docs/data/base/pagesApi.js +++ b/docs/data/base/pagesApi.js @@ -169,6 +169,22 @@ module.exports = [ { pathname: '/base-ui/react-popper/components-api/#popper', title: 'Popper' }, { pathname: '/base-ui/react-popup/components-api/#popup', title: 'Popup' }, { pathname: '/base-ui/react-portal/components-api/#portal', title: 'Portal' }, + { + pathname: '/base-ui/react-progress/components-api/#progress-buffer', + title: 'ProgressBuffer', + }, + { + pathname: '/base-ui/react-progress/components-api/#progress-indicator', + title: 'ProgressIndicator', + }, + { + pathname: '/base-ui/react-progress/components-api/#progress-root', + title: 'ProgressRoot', + }, + { + pathname: '/base-ui/react-progress/components-api/#progress-track', + title: 'ProgressTrack', + }, { pathname: '/base-ui/react-select/components-api/#select', title: 'Select' }, { pathname: '/base-ui/react-slider/components-api/#slider-control', @@ -298,6 +314,18 @@ module.exports = [ pathname: '/base-ui/react-select/hooks-api/#use-option-context-stabilizer', title: 'useOptionContextStabilizer', }, + { + pathname: '/base-ui/react-progress/hooks-api/#use-progress-buffer', + title: 'useProgressBuffer', + }, + { + pathname: '/base-ui/react-progress/hooks-api/#use-progress-indicator', + title: 'useProgressIndicator', + }, + { + pathname: '/base-ui/react-progress/hooks-api/#use-progress-root', + title: 'useProgressRoot', + }, { pathname: '/base-ui/react-select/hooks-api/#use-select', title: 'useSelect' }, { pathname: '/base-ui/react-slider/hooks-api/#use-slider-control', diff --git a/docs/pages/base-ui/api/progress-buffer.json b/docs/pages/base-ui/api/progress-buffer.json new file mode 100644 index 0000000000..2a4c96ebd3 --- /dev/null +++ b/docs/pages/base-ui/api/progress-buffer.json @@ -0,0 +1,16 @@ +{ + "props": { + "className": { "type": { "name": "union", "description": "func
| string" } }, + "render": { "type": { "name": "union", "description": "element
| func" } } + }, + "name": "ProgressBuffer", + "imports": [ + "import * as Progress from '@base_ui/react/Progress';\nconst ProgressBuffer = Progress.Buffer;" + ], + "classes": [], + "muiName": "ProgressBuffer", + "filename": "/packages/mui-base/src/Progress/ProgressBuffer/ProgressBuffer.tsx", + "inheritance": null, + "demos": "", + "cssComponent": false +} diff --git a/docs/pages/base-ui/api/progress-indicator.json b/docs/pages/base-ui/api/progress-indicator.json new file mode 100644 index 0000000000..a66151747b --- /dev/null +++ b/docs/pages/base-ui/api/progress-indicator.json @@ -0,0 +1,16 @@ +{ + "props": { + "className": { "type": { "name": "union", "description": "func
| string" } }, + "render": { "type": { "name": "union", "description": "element
| func" } } + }, + "name": "ProgressIndicator", + "imports": [ + "import * as Progress from '@base_ui/react/Progress';\nconst ProgressIndicator = Progress.Indicator;" + ], + "classes": [], + "muiName": "ProgressIndicator", + "filename": "/packages/mui-base/src/Progress/ProgressIndicator/ProgressIndicator.tsx", + "inheritance": null, + "demos": "", + "cssComponent": false +} diff --git a/docs/pages/base-ui/api/progress-root.json b/docs/pages/base-ui/api/progress-root.json new file mode 100644 index 0000000000..8d17afee93 --- /dev/null +++ b/docs/pages/base-ui/api/progress-root.json @@ -0,0 +1,41 @@ +{ + "props": { + "aria-label": { "type": { "name": "string" } }, + "aria-labelledby": { "type": { "name": "string" } }, + "aria-valuetext": { "type": { "name": "string" } }, + "bufferValue": { "type": { "name": "number" } }, + "className": { "type": { "name": "union", "description": "func
| string" } }, + "direction": { + "type": { "name": "enum", "description": "'ltr'
| 'rtl'" }, + "default": "'ltr'" + }, + "getAriaLabel": { + "type": { "name": "func" }, + "signature": { + "type": "function(value: number | null) => string", + "describedArgs": ["value"] + } + }, + "getAriaValueText": { + "type": { "name": "func" }, + "signature": { + "type": "function(value: number | null) => string", + "describedArgs": ["value"] + } + }, + "max": { "type": { "name": "number" }, "default": "100" }, + "min": { "type": { "name": "number" }, "default": "0" }, + "render": { "type": { "name": "union", "description": "element
| func" } }, + "value": { "type": { "name": "number" }, "default": "null" } + }, + "name": "ProgressRoot", + "imports": [ + "import * as Progress from '@base_ui/react/Progress';\nconst ProgressRoot = Progress.Root;" + ], + "classes": [], + "muiName": "ProgressRoot", + "filename": "/packages/mui-base/src/Progress/Root/ProgressRoot.tsx", + "inheritance": null, + "demos": "", + "cssComponent": false +} diff --git a/docs/pages/base-ui/api/progress-track.json b/docs/pages/base-ui/api/progress-track.json new file mode 100644 index 0000000000..8758f5eeb4 --- /dev/null +++ b/docs/pages/base-ui/api/progress-track.json @@ -0,0 +1,16 @@ +{ + "props": { + "className": { "type": { "name": "union", "description": "func
| string" } }, + "render": { "type": { "name": "union", "description": "element
| func" } } + }, + "name": "ProgressTrack", + "imports": [ + "import * as Progress from '@base_ui/react/Progress';\nconst ProgressTrack = Progress.Track;" + ], + "classes": [], + "muiName": "ProgressTrack", + "filename": "/packages/mui-base/src/Progress/ProgressTrack/ProgressTrack.tsx", + "inheritance": null, + "demos": "", + "cssComponent": false +} diff --git a/docs/pages/base-ui/api/use-progress-buffer.json b/docs/pages/base-ui/api/use-progress-buffer.json new file mode 100644 index 0000000000..26bbd21cf9 --- /dev/null +++ b/docs/pages/base-ui/api/use-progress-buffer.json @@ -0,0 +1,24 @@ +{ + "parameters": { + "bufferValue": { "type": { "name": "number", "description": "number" }, "required": true }, + "direction": { + "type": { "name": "ProgressDirection", "description": "ProgressDirection" }, + "default": "'ltr'" + }, + "max": { "type": { "name": "number", "description": "number" }, "default": "100" }, + "min": { "type": { "name": "number", "description": "number" }, "default": "0" } + }, + "returnValue": { + "getRootProps": { + "type": { + "name": "(externalProps?: React.ComponentPropsWithRef<'span'>) => React.ComponentPropsWithRef<'span'>", + "description": "(externalProps?: React.ComponentPropsWithRef<'span'>) => React.ComponentPropsWithRef<'span'>" + }, + "required": true + } + }, + "name": "useProgressBuffer", + "filename": "/packages/mui-base/src/Progress/ProgressBuffer/useProgressBuffer.ts", + "imports": ["import { useProgressBuffer } from '@base_ui/react/Progress';"], + "demos": "" +} diff --git a/docs/pages/base-ui/api/use-progress-indicator.json b/docs/pages/base-ui/api/use-progress-indicator.json new file mode 100644 index 0000000000..0ffc64683e --- /dev/null +++ b/docs/pages/base-ui/api/use-progress-indicator.json @@ -0,0 +1,28 @@ +{ + "parameters": { + "value": { + "type": { "name": "number | null", "description": "number | null" }, + "default": "null", + "required": true + }, + "direction": { + "type": { "name": "ProgressDirection", "description": "ProgressDirection" }, + "default": "'ltr'" + }, + "max": { "type": { "name": "number", "description": "number" }, "default": "100" }, + "min": { "type": { "name": "number", "description": "number" }, "default": "0" } + }, + "returnValue": { + "getRootProps": { + "type": { + "name": "(externalProps?: React.ComponentPropsWithRef<'span'>) => React.ComponentPropsWithRef<'span'>", + "description": "(externalProps?: React.ComponentPropsWithRef<'span'>) => React.ComponentPropsWithRef<'span'>" + }, + "required": true + } + }, + "name": "useProgressIndicator", + "filename": "/packages/mui-base/src/Progress/ProgressIndicator/useProgressIndicator.ts", + "imports": ["import { useProgressIndicator } from '@base_ui/react/Progress';"], + "demos": "" +} diff --git a/docs/pages/base-ui/api/use-progress-root.json b/docs/pages/base-ui/api/use-progress-root.json new file mode 100644 index 0000000000..3bb95b96a4 --- /dev/null +++ b/docs/pages/base-ui/api/use-progress-root.json @@ -0,0 +1,59 @@ +{ + "parameters": { + "value": { + "type": { "name": "number | null", "description": "number | null" }, + "default": "null", + "required": true + }, + "aria-label": { "type": { "name": "string", "description": "string" } }, + "aria-labelledby": { "type": { "name": "string", "description": "string" } }, + "aria-valuetext": { "type": { "name": "string", "description": "string" } }, + "bufferValue": { "type": { "name": "number", "description": "number" } }, + "direction": { + "type": { "name": "ProgressDirection", "description": "ProgressDirection" }, + "default": "'ltr'" + }, + "getAriaLabel": { + "type": { + "name": "(index: number | null) => string", + "description": "(index: number | null) => string" + } + }, + "getAriaValueText": { + "type": { + "name": "(value: number | null, bufferValue: number | undefined) => string", + "description": "(value: number | null, bufferValue: number | undefined) => string" + } + }, + "max": { "type": { "name": "number", "description": "number" }, "default": "100" }, + "min": { "type": { "name": "number", "description": "number" }, "default": "0" } + }, + "returnValue": { + "direction": { + "type": { "name": "ProgressDirection", "description": "ProgressDirection" }, + "required": true + }, + "getRootProps": { + "type": { + "name": "(externalProps?: React.ComponentPropsWithRef<'div'>) => React.ComponentPropsWithRef<'div'>", + "description": "(externalProps?: React.ComponentPropsWithRef<'div'>) => React.ComponentPropsWithRef<'div'>" + }, + "required": true + }, + "max": { "type": { "name": "number", "description": "number" }, "required": true }, + "min": { "type": { "name": "number", "description": "number" }, "required": true }, + "state": { + "type": { "name": "ProgressStatus", "description": "ProgressStatus" }, + "required": true + }, + "value": { + "type": { "name": "number | null", "description": "number | null" }, + "required": true + }, + "bufferValue": { "type": { "name": "number", "description": "number" } } + }, + "name": "useProgressRoot", + "filename": "/packages/mui-base/src/Progress/Root/useProgressRoot.ts", + "imports": ["import { useProgressRoot } from '@base_ui/react/Progress';"], + "demos": "" +} diff --git a/docs/pages/base-ui/react-progress/[docsTab]/index.js b/docs/pages/base-ui/react-progress/[docsTab]/index.js new file mode 100644 index 0000000000..61475917a9 --- /dev/null +++ b/docs/pages/base-ui/react-progress/[docsTab]/index.js @@ -0,0 +1,106 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocsV2'; +import AppFrame from 'docs/src/modules/components/AppFrame'; +import * as pageProps from 'docs-base/data/base/components/progress/progress.md?@mui/markdown'; +import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations'; +import ProgressBufferApiJsonPageContent from '../../api/progress-buffer.json'; +import ProgressIndicatorApiJsonPageContent from '../../api/progress-indicator.json'; +import ProgressRootApiJsonPageContent from '../../api/progress-root.json'; +import ProgressTrackApiJsonPageContent from '../../api/progress-track.json'; +import useProgressBufferApiJsonPageContent from '../../api/use-progress-buffer.json'; +import useProgressIndicatorApiJsonPageContent from '../../api/use-progress-indicator.json'; +import useProgressRootApiJsonPageContent from '../../api/use-progress-root.json'; + +export default function Page(props) { + const { userLanguage, ...other } = props; + return ; +} + +Page.getLayout = (page) => { + return {page}; +}; + +export const getStaticPaths = () => { + return { + paths: [{ params: { docsTab: 'components-api' } }, { params: { docsTab: 'hooks-api' } }], + fallback: false, // can also be true or 'blocking' + }; +}; + +export const getStaticProps = () => { + const ProgressBufferApiReq = require.context( + 'docs-base/translations/api-docs/progress-buffer', + false, + /\.\/progress-buffer.*.json$/, + ); + const ProgressBufferApiDescriptions = mapApiPageTranslations(ProgressBufferApiReq); + + const ProgressIndicatorApiReq = require.context( + 'docs-base/translations/api-docs/progress-indicator', + false, + /\.\/progress-indicator.*.json$/, + ); + const ProgressIndicatorApiDescriptions = mapApiPageTranslations(ProgressIndicatorApiReq); + + const ProgressRootApiReq = require.context( + 'docs-base/translations/api-docs/progress-root', + false, + /\.\/progress-root.*.json$/, + ); + const ProgressRootApiDescriptions = mapApiPageTranslations(ProgressRootApiReq); + + const ProgressTrackApiReq = require.context( + 'docs-base/translations/api-docs/progress-track', + false, + /\.\/progress-track.*.json$/, + ); + const ProgressTrackApiDescriptions = mapApiPageTranslations(ProgressTrackApiReq); + + const useProgressBufferApiReq = require.context( + 'docs-base/translations/api-docs/use-progress-buffer', + false, + /\.\/use-progress-buffer.*.json$/, + ); + const useProgressBufferApiDescriptions = mapApiPageTranslations(useProgressBufferApiReq); + + const useProgressIndicatorApiReq = require.context( + 'docs-base/translations/api-docs/use-progress-indicator', + false, + /\.\/use-progress-indicator.*.json$/, + ); + const useProgressIndicatorApiDescriptions = mapApiPageTranslations(useProgressIndicatorApiReq); + + const useProgressRootApiReq = require.context( + 'docs-base/translations/api-docs/use-progress-root', + false, + /\.\/use-progress-root.*.json$/, + ); + const useProgressRootApiDescriptions = mapApiPageTranslations(useProgressRootApiReq); + + return { + props: { + componentsApiDescriptions: { + ProgressBuffer: ProgressBufferApiDescriptions, + ProgressIndicator: ProgressIndicatorApiDescriptions, + ProgressRoot: ProgressRootApiDescriptions, + ProgressTrack: ProgressTrackApiDescriptions, + }, + componentsApiPageContents: { + ProgressBuffer: ProgressBufferApiJsonPageContent, + ProgressIndicator: ProgressIndicatorApiJsonPageContent, + ProgressRoot: ProgressRootApiJsonPageContent, + ProgressTrack: ProgressTrackApiJsonPageContent, + }, + hooksApiDescriptions: { + useProgressBuffer: useProgressBufferApiDescriptions, + useProgressIndicator: useProgressIndicatorApiDescriptions, + useProgressRoot: useProgressRootApiDescriptions, + }, + hooksApiPageContents: { + useProgressBuffer: useProgressBufferApiJsonPageContent, + useProgressIndicator: useProgressIndicatorApiJsonPageContent, + useProgressRoot: useProgressRootApiJsonPageContent, + }, + }, + }; +}; diff --git a/docs/pages/base-ui/react-progress/index.js b/docs/pages/base-ui/react-progress/index.js new file mode 100644 index 0000000000..ae0ef5d8da --- /dev/null +++ b/docs/pages/base-ui/react-progress/index.js @@ -0,0 +1,13 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocsV2'; +import AppFrame from 'docs/src/modules/components/AppFrame'; +import * as pageProps from 'docs-base/data/base/components/progress/progress.md?@mui/markdown'; + +export default function Page(props) { + const { userLanguage, ...other } = props; + return ; +} + +Page.getLayout = (page) => { + return {page}; +}; diff --git a/docs/pages/experiments/progress.tsx b/docs/pages/experiments/progress.tsx new file mode 100644 index 0000000000..f3e7d34333 --- /dev/null +++ b/docs/pages/experiments/progress.tsx @@ -0,0 +1,261 @@ +import * as React from 'react'; +import { useTheme } from '@mui/system'; +import * as Progress from '@base_ui/react/Progress'; + +const VAL1 = 33; + +const CUSTOM_BUFFER_VAL = 77; + +export default function ProgressDemos() { + return ( +
+ + + + + + + + + Indeterminate Progress + + + + + + + `${value}% complete, ${bufferValue}% buffered`} + > + + Buffered + + + + + + + + + + Progress (RTL) + + + + + + + + + Indeterminate (RTL) + + + + + + +

Customizations

+ + + + + {VAL1}% + + + + + `${value}% complete, ${CUSTOM_BUFFER_VAL}% buffered`} + max={Math.min(100, CUSTOM_BUFFER_VAL)} + > + + Custom Buffer Component + + + + + + + + `${value}% complete, ${CUSTOM_BUFFER_VAL}% buffered`} + max={Math.min(100, CUSTOM_BUFFER_VAL)} + direction="rtl" + > + + Custom Buffer Component (RTL) + + + + + + + +
+ ); +} + +function MyBuffer(props: any) { + const { value, style, ...rest } = props; + const percentageValue = valueToPercent(value, 0, 100); + return ( + + ); +} + +function valueToPercent(value: number | undefined, min: number, max: number) { + if (value === undefined) { + return value; + } + + return ((value - min) * 100) / (max - min); +} + +const grey = { + 50: '#F3F6F9', + 100: '#E5EAF2', + 200: '#DAE2ED', + 300: '#C7D0DD', + 400: '#B0B8C4', + 500: '#9DA8B7', + 600: '#6B7A90', + 700: '#434D5B', + 800: '#303740', + 900: '#1C2025', +}; + +const BLUE400 = '#3399FF'; +const BLUE500 = '#007FFF'; + +function useIsDarkMode() { + const theme = useTheme(); + return theme.palette.mode === 'dark'; +} + +export function Styles() { + const isDarkMode = useIsDarkMode(); + return ( + + ); +} diff --git a/docs/translations/api-docs/progress-buffer/progress-buffer.json b/docs/translations/api-docs/progress-buffer/progress-buffer.json new file mode 100644 index 0000000000..4bc12cf1e0 --- /dev/null +++ b/docs/translations/api-docs/progress-buffer/progress-buffer.json @@ -0,0 +1,10 @@ +{ + "componentDescription": "", + "propDescriptions": { + "className": { + "description": "Class names applied to the element or a function that returns them based on the component's state." + }, + "render": { "description": "A function to customize rendering of the component." } + }, + "classDescriptions": {} +} diff --git a/docs/translations/api-docs/progress-indicator/progress-indicator.json b/docs/translations/api-docs/progress-indicator/progress-indicator.json new file mode 100644 index 0000000000..4bc12cf1e0 --- /dev/null +++ b/docs/translations/api-docs/progress-indicator/progress-indicator.json @@ -0,0 +1,10 @@ +{ + "componentDescription": "", + "propDescriptions": { + "className": { + "description": "Class names applied to the element or a function that returns them based on the component's state." + }, + "render": { "description": "A function to customize rendering of the component." } + }, + "classDescriptions": {} +} diff --git a/docs/translations/api-docs/progress-root/progress-root.json b/docs/translations/api-docs/progress-root/progress-root.json new file mode 100644 index 0000000000..485186c9ed --- /dev/null +++ b/docs/translations/api-docs/progress-root/progress-root.json @@ -0,0 +1,32 @@ +{ + "componentDescription": "", + "propDescriptions": { + "aria-label": { "description": "The label for the Indicator component." }, + "aria-labelledby": { + "description": "An id or space-separated list of ids of elements that label the Indicator component." + }, + "aria-valuetext": { + "description": "A string value that provides a human-readable text alternative for the current value of the progress indicator." + }, + "bufferValue": { "description": "Optional buffer value" }, + "className": { + "description": "Class names applied to the element or a function that returns them based on the component's state." + }, + "direction": { "description": "The direction that progress bars fill in" }, + "getAriaLabel": { + "description": "Accepts a function which returns a string value that provides an accessible name for the Indicator component", + "typeDescriptions": { "value": "The component's value" } + }, + "getAriaValueText": { + "description": "Accepts a function which returns a string value that provides a human-readable text alternative for the current value of the progress indicator.", + "typeDescriptions": { "value": "The component's value to format" } + }, + "max": { "description": "The maximum value" }, + "min": { "description": "The minimum value" }, + "render": { "description": "A function to customize rendering of the component." }, + "value": { + "description": "The current value. The component is indeterminate when value is null." + } + }, + "classDescriptions": {} +} diff --git a/docs/translations/api-docs/progress-track/progress-track.json b/docs/translations/api-docs/progress-track/progress-track.json new file mode 100644 index 0000000000..4bc12cf1e0 --- /dev/null +++ b/docs/translations/api-docs/progress-track/progress-track.json @@ -0,0 +1,10 @@ +{ + "componentDescription": "", + "propDescriptions": { + "className": { + "description": "Class names applied to the element or a function that returns them based on the component's state." + }, + "render": { "description": "A function to customize rendering of the component." } + }, + "classDescriptions": {} +} diff --git a/docs/translations/api-docs/use-progress-buffer/use-progress-buffer.json b/docs/translations/api-docs/use-progress-buffer/use-progress-buffer.json new file mode 100644 index 0000000000..ab4c89bd02 --- /dev/null +++ b/docs/translations/api-docs/use-progress-buffer/use-progress-buffer.json @@ -0,0 +1,10 @@ +{ + "hookDescription": "", + "parametersDescriptions": { + "bufferValue": { "description": "The buffer value." }, + "direction": { "description": "The direction that progress bars fill in" }, + "max": { "description": "The maximum value" }, + "min": { "description": "The minimum value" } + }, + "returnValueDescriptions": {} +} diff --git a/docs/translations/api-docs/use-progress-indicator/use-progress-indicator.json b/docs/translations/api-docs/use-progress-indicator/use-progress-indicator.json new file mode 100644 index 0000000000..3c787fafc5 --- /dev/null +++ b/docs/translations/api-docs/use-progress-indicator/use-progress-indicator.json @@ -0,0 +1,12 @@ +{ + "hookDescription": "", + "parametersDescriptions": { + "direction": { "description": "The direction that progress bars fill in" }, + "max": { "description": "The maximum value" }, + "min": { "description": "The minimum value" }, + "value": { + "description": "The current value. The component is indeterminate when value is null." + } + }, + "returnValueDescriptions": {} +} diff --git a/docs/translations/api-docs/use-progress-root/use-progress-root.json b/docs/translations/api-docs/use-progress-root/use-progress-root.json new file mode 100644 index 0000000000..f497d5c89f --- /dev/null +++ b/docs/translations/api-docs/use-progress-root/use-progress-root.json @@ -0,0 +1,32 @@ +{ + "hookDescription": "", + "parametersDescriptions": { + "aria-label": { "description": "The label for the Indicator component." }, + "aria-labelledby": { + "description": "An id or space-separated list of ids of elements that label the Indicator component." + }, + "aria-valuetext": { + "description": "A string value that provides a human-readable text alternative for the current value of the progress indicator." + }, + "bufferValue": { "description": "Optional buffer value" }, + "direction": { "description": "The direction that progress bars fill in" }, + "getAriaLabel": { + "description": "Accepts a function which returns a string value that provides an accessible name for the Indicator component" + }, + "getAriaValueText": { + "description": "Accepts a function which returns a string value that provides a human-readable text alternative for the current value of the progress indicator." + }, + "max": { "description": "The maximum value" }, + "min": { "description": "The minimum value" }, + "value": { + "description": "The current value. The component is indeterminate when value is null." + } + }, + "returnValueDescriptions": { + "bufferValue": { "description": "Value of the buffer" }, + "direction": { "description": "The direction that progress bars fill in" }, + "max": { "description": "The maximum value" }, + "min": { "description": "The minimum value" }, + "value": { "description": "Value of the component" } + } +} diff --git a/packages/mui-base/src/Progress/Buffer/ProgressBuffer.tsx b/packages/mui-base/src/Progress/Buffer/ProgressBuffer.tsx new file mode 100644 index 0000000000..e9f9347d6f --- /dev/null +++ b/packages/mui-base/src/Progress/Buffer/ProgressBuffer.tsx @@ -0,0 +1,61 @@ +'use client'; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { useComponentRenderer } from '../../utils/useComponentRenderer'; +import { useProgressBuffer } from './useProgressBuffer'; +import { useProgressContext } from '../Root/ProgressContext'; +import { progressStyleHookMapping } from '../Root/styleHooks'; +import { ProgressBufferProps } from './ProgressBuffer.types'; + +const ProgressBuffer = React.forwardRef(function ProgressBuffer( + props: ProgressBufferProps, + forwardedRef: React.ForwardedRef, +) { + const { render, className, ...otherProps } = props; + + const { direction, max, min, bufferValue, ownerState } = useProgressContext(); + + if (!Number.isFinite(bufferValue)) { + throw new Error(' must be used with a `bufferValue`'); + } + + const { getRootProps } = useProgressBuffer({ + direction, + max, + min, + bufferValue: bufferValue ?? 0, + }); + + const { renderElement } = useComponentRenderer({ + propGetter: getRootProps, + render: render ?? 'span', + ownerState, + className, + ref: forwardedRef, + extraProps: otherProps, + customStyleHookMapping: progressStyleHookMapping, + }); + + return renderElement(); +}); + +ProgressBuffer.propTypes /* remove-proptypes */ = { + // ┌────────────────────────────── Warning ──────────────────────────────┐ + // │ These PropTypes are generated from the TypeScript type definitions. │ + // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ + // └─────────────────────────────────────────────────────────────────────┘ + /** + * @ignore + */ + children: PropTypes.node, + /** + * Class names applied to the element or a function that returns them based on the component's state. + */ + className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + /** + * A function to customize rendering of the component. + */ + render: PropTypes.oneOfType([PropTypes.element, PropTypes.func]), +} as any; + +export { ProgressBuffer }; diff --git a/packages/mui-base/src/Progress/Buffer/ProgressBuffer.types.ts b/packages/mui-base/src/Progress/Buffer/ProgressBuffer.types.ts new file mode 100644 index 0000000000..5fa8f83d09 --- /dev/null +++ b/packages/mui-base/src/Progress/Buffer/ProgressBuffer.types.ts @@ -0,0 +1,32 @@ +import { BaseUIComponentProps } from '../../utils/types'; +import { ProgressDirection, ProgressRootOwnerState } from '../Root/ProgressRoot.types'; + +export interface ProgressBufferProps extends BaseUIComponentProps<'span', ProgressRootOwnerState> {} + +export interface UseProgressBufferParameters { + /** + * The direction that progress bars fill in + * @default 'ltr' + */ + direction?: ProgressDirection; + /** + * The maximum value + * @default 100 + */ + max?: number; + /** + * The minimum value + * @default 0 + */ + min?: number; + /** + * The buffer value. + */ + bufferValue: number; +} + +export interface UseProgressBufferReturnValue { + getRootProps: ( + externalProps?: React.ComponentPropsWithRef<'span'>, + ) => React.ComponentPropsWithRef<'span'>; +} diff --git a/packages/mui-base/src/Progress/Buffer/useProgressBuffer.ts b/packages/mui-base/src/Progress/Buffer/useProgressBuffer.ts new file mode 100644 index 0000000000..8d6bc3caf8 --- /dev/null +++ b/packages/mui-base/src/Progress/Buffer/useProgressBuffer.ts @@ -0,0 +1,55 @@ +'use client'; +import * as React from 'react'; +import { mergeReactProps } from '../../utils/mergeReactProps'; +import { UseProgressBufferParameters, UseProgressBufferReturnValue } from './ProgressBuffer.types'; + +function valueToPercent(value: number | undefined, min: number, max: number) { + if (value === undefined) { + return value; + } + + return ((value - min) * 100) / (max - min); +} +/** + * + * Demos: + * + * - [Progress](https://mui.com/base-ui/react-progress/#hooks) + * + * API: + * + * - [useProgressBuffer API](https://mui.com/base-ui/react-progress/hooks-api/#use-progress-buffer) + */ +function useProgressBuffer(parameters: UseProgressBufferParameters): UseProgressBufferReturnValue { + const { direction, max = 100, min = 0, bufferValue } = parameters; + + const isRtl = direction === 'rtl'; + + const percentageValue = valueToPercent(bufferValue, min, max); + + const getStyles = React.useCallback(() => { + return { + [isRtl ? 'right' : 'left']: 0, + width: percentageValue ? `${percentageValue}%` : undefined, + }; + }, [isRtl, percentageValue]); + + const getRootProps: UseProgressBufferReturnValue['getRootProps'] = React.useCallback( + (externalProps = {}) => + mergeReactProps<'span'>(externalProps, { + style: { + height: 'inherit', + position: 'absolute', + zIndex: 0, + ...getStyles(), + }, + }), + [getStyles], + ); + + return { + getRootProps, + }; +} + +export { useProgressBuffer }; diff --git a/packages/mui-base/src/Progress/Indicator/ProgressIndicator.tsx b/packages/mui-base/src/Progress/Indicator/ProgressIndicator.tsx new file mode 100644 index 0000000000..180072c34c --- /dev/null +++ b/packages/mui-base/src/Progress/Indicator/ProgressIndicator.tsx @@ -0,0 +1,57 @@ +'use client'; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { useComponentRenderer } from '../../utils/useComponentRenderer'; +import { useProgressIndicator } from './useProgressIndicator'; +import { useProgressContext } from '../Root/ProgressContext'; +import { progressStyleHookMapping } from '../Root/styleHooks'; +import { ProgressIndicatorProps } from './ProgressIndicator.types'; + +const ProgressIndicator = React.forwardRef(function ProgressIndicator( + props: ProgressIndicatorProps, + forwardedRef: React.ForwardedRef, +) { + const { render, className, ...otherProps } = props; + + const { direction, max, min, value, ownerState } = useProgressContext(); + + const { getRootProps } = useProgressIndicator({ + direction, + max, + min, + value, + }); + + const { renderElement } = useComponentRenderer({ + propGetter: getRootProps, + render: render ?? 'span', + ownerState, + className, + ref: forwardedRef, + extraProps: otherProps, + customStyleHookMapping: progressStyleHookMapping, + }); + + return renderElement(); +}); + +ProgressIndicator.propTypes /* remove-proptypes */ = { + // ┌────────────────────────────── Warning ──────────────────────────────┐ + // │ These PropTypes are generated from the TypeScript type definitions. │ + // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ + // └─────────────────────────────────────────────────────────────────────┘ + /** + * @ignore + */ + children: PropTypes.node, + /** + * Class names applied to the element or a function that returns them based on the component's state. + */ + className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + /** + * A function to customize rendering of the component. + */ + render: PropTypes.oneOfType([PropTypes.element, PropTypes.func]), +} as any; + +export { ProgressIndicator }; diff --git a/packages/mui-base/src/Progress/Indicator/ProgressIndicator.types.ts b/packages/mui-base/src/Progress/Indicator/ProgressIndicator.types.ts new file mode 100644 index 0000000000..8eabc29f1c --- /dev/null +++ b/packages/mui-base/src/Progress/Indicator/ProgressIndicator.types.ts @@ -0,0 +1,34 @@ +import { BaseUIComponentProps } from '../../utils/types'; +import { ProgressDirection, ProgressRootOwnerState } from '../Root/ProgressRoot.types'; + +export interface ProgressIndicatorProps + extends BaseUIComponentProps<'span', ProgressRootOwnerState> {} + +export interface UseProgressIndicatorParameters { + /** + * The direction that progress bars fill in + * @default 'ltr' + */ + direction?: ProgressDirection; + /** + * The maximum value + * @default 100 + */ + max?: number; + /** + * The minimum value + * @default 0 + */ + min?: number; + /** + * The current value. The component is indeterminate when value is `null`. + * @default null + */ + value: number | null; +} + +export interface UseProgressIndicatorReturnValue { + getRootProps: ( + externalProps?: React.ComponentPropsWithRef<'span'>, + ) => React.ComponentPropsWithRef<'span'>; +} diff --git a/packages/mui-base/src/Progress/Indicator/useProgressIndicator.ts b/packages/mui-base/src/Progress/Indicator/useProgressIndicator.ts new file mode 100644 index 0000000000..474d951b7c --- /dev/null +++ b/packages/mui-base/src/Progress/Indicator/useProgressIndicator.ts @@ -0,0 +1,56 @@ +'use client'; +import * as React from 'react'; +import { mergeReactProps } from '../../utils/mergeReactProps'; +import { + UseProgressIndicatorParameters, + UseProgressIndicatorReturnValue, +} from './ProgressIndicator.types'; + +function valueToPercent(value: number, min: number, max: number) { + return ((value - min) * 100) / (max - min); +} +/** + * + * Demos: + * + * - [Progress](https://mui.com/base-ui/react-progress/#hooks) + * + * API: + * + * - [useProgressIndicator API](https://mui.com/base-ui/react-progress/hooks-api/#use-progress-indicator) + */ +function useProgressIndicator( + parameters: UseProgressIndicatorParameters, +): UseProgressIndicatorReturnValue { + const { direction, max = 100, min = 0, value } = parameters; + + const isRtl = direction === 'rtl'; + + const percentageValue = + Number.isFinite(value) && value !== null ? valueToPercent(value, min, max) : null; + + const getStyles = React.useCallback(() => { + return { + [isRtl ? 'right' : 'left']: 0, + width: percentageValue ? `${percentageValue}%` : undefined, + }; + }, [isRtl, percentageValue]); + + const getRootProps: UseProgressIndicatorReturnValue['getRootProps'] = React.useCallback( + (externalProps = {}) => + mergeReactProps<'span'>(externalProps, { + style: { + height: 'inherit', + zIndex: 1, + ...getStyles(), + }, + }), + [getStyles], + ); + + return { + getRootProps, + }; +} + +export { useProgressIndicator }; diff --git a/packages/mui-base/src/Progress/Root/ProgressContext.tsx b/packages/mui-base/src/Progress/Root/ProgressContext.tsx new file mode 100644 index 0000000000..cfdddc11a1 --- /dev/null +++ b/packages/mui-base/src/Progress/Root/ProgressContext.tsx @@ -0,0 +1,25 @@ +'use client'; +import * as React from 'react'; +import { ProgressContextValue } from './ProgressRoot.types'; + +export interface ProgressProviderProps { + value: ProgressContextValue; + children: React.ReactNode; +} + +/** + * @ignore - internal component. + */ +export const ProgressContext = React.createContext(undefined); + +if (process.env.NODE_ENV !== 'production') { + ProgressContext.displayName = 'ProgressContext'; +} + +export function useProgressContext() { + const context = React.useContext(ProgressContext); + if (context === undefined) { + throw new Error('useProgressContext must be used inside a Progress component'); + } + return context; +} diff --git a/packages/mui-base/src/Progress/Root/ProgressRoot.tsx b/packages/mui-base/src/Progress/Root/ProgressRoot.tsx new file mode 100644 index 0000000000..48c75fb37a --- /dev/null +++ b/packages/mui-base/src/Progress/Root/ProgressRoot.tsx @@ -0,0 +1,147 @@ +'use client'; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { useComponentRenderer } from '../../utils/useComponentRenderer'; +import { useProgressRoot } from './useProgressRoot'; +import { ProgressContext } from './ProgressContext'; +import { progressStyleHookMapping } from './styleHooks'; +import { + ProgressContextValue, + ProgressRootOwnerState, + ProgressRootProps, +} from './ProgressRoot.types'; + +const ProgressRoot = React.forwardRef(function ProgressRoot( + props: ProgressRootProps, + forwardedRef: React.ForwardedRef, +) { + const { + 'aria-label': ariaLabel, + 'aria-labelledby': ariaLabelledby, + 'aria-valuetext': ariaValuetext, + bufferValue, + direction = 'ltr', + getAriaLabel, + getAriaValueText, + max = 100, + min = 0, + value, + render, + className, + ...otherProps + } = props; + + const { getRootProps, ...progress } = useProgressRoot({ + 'aria-label': ariaLabel, + 'aria-labelledby': ariaLabelledby, + 'aria-valuetext': ariaValuetext, + direction, + getAriaLabel, + getAriaValueText, + max, + min, + value, + bufferValue, + }); + + const ownerState: ProgressRootOwnerState = React.useMemo( + () => ({ + direction, + max, + min, + state: progress.state, + }), + [direction, max, min, progress.state], + ); + + const contextValue: ProgressContextValue = React.useMemo( + () => ({ + ...progress, + ownerState, + }), + [progress, ownerState], + ); + + const { renderElement } = useComponentRenderer({ + propGetter: getRootProps, + render: render ?? 'div', + ownerState, + className, + ref: forwardedRef, + extraProps: otherProps, + customStyleHookMapping: progressStyleHookMapping, + }); + + return ( + {renderElement()} + ); +}); + +ProgressRoot.propTypes /* remove-proptypes */ = { + // ┌────────────────────────────── Warning ──────────────────────────────┐ + // │ These PropTypes are generated from the TypeScript type definitions. │ + // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ + // └─────────────────────────────────────────────────────────────────────┘ + /** + * The label for the Indicator component. + */ + 'aria-label': PropTypes.string, + /** + * An id or space-separated list of ids of elements that label the Indicator component. + */ + 'aria-labelledby': PropTypes.string, + /** + * A string value that provides a human-readable text alternative for the current value of the progress indicator. + */ + 'aria-valuetext': PropTypes.string, + /** + * Optional buffer value + */ + bufferValue: PropTypes.number, + /** + * @ignore + */ + children: PropTypes.node, + /** + * Class names applied to the element or a function that returns them based on the component's state. + */ + className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + /** + * The direction that progress bars fill in + * @default 'ltr' + */ + direction: PropTypes.oneOf(['ltr', 'rtl']), + /** + * Accepts a function which returns a string value that provides an accessible name for the Indicator component + * @param {number | null} value The component's value + * @returns {string} + */ + getAriaLabel: PropTypes.func, + /** + * Accepts a function which returns a string value that provides a human-readable text alternative for the current value of the progress indicator. + * @param {number | null} value The component's value to format + * @returns {string} + */ + getAriaValueText: PropTypes.func, + /** + * The maximum value + * @default 100 + */ + max: PropTypes.number, + /** + * The minimum value + * @default 0 + */ + min: PropTypes.number, + /** + * A function to customize rendering of the component. + */ + render: PropTypes.oneOfType([PropTypes.element, PropTypes.func]), + /** + * The current value. The component is indeterminate when value is `null`. + * @default null + */ + value: PropTypes.number, +} as any; + +export { ProgressRoot }; diff --git a/packages/mui-base/src/Progress/Root/ProgressRoot.types.ts b/packages/mui-base/src/Progress/Root/ProgressRoot.types.ts new file mode 100644 index 0000000000..44ab77c800 --- /dev/null +++ b/packages/mui-base/src/Progress/Root/ProgressRoot.types.ts @@ -0,0 +1,97 @@ +import { BaseUIComponentProps } from '../../utils/types'; + +export type ProgressContextValue = Omit & { + ownerState: ProgressRootOwnerState; +}; + +export type ProgressRootOwnerState = { + direction: ProgressDirection; + max: number; + min: number; +}; + +export interface ProgressRootProps + extends UseProgressRootParameters, + BaseUIComponentProps<'div', ProgressRootOwnerState> {} + +export type ProgressDirection = 'ltr' | 'rtl'; + +export type ProgressStatus = 'indeterminate' | 'loading' | 'complete'; + +export interface UseProgressRootParameters { + /** + * The label for the Indicator component. + */ + 'aria-label'?: string; + /** + * An id or space-separated list of ids of elements that label the Indicator component. + */ + 'aria-labelledby'?: string; + /** + * A string value that provides a human-readable text alternative for the current value of the progress indicator. + */ + 'aria-valuetext'?: string; + /** + * Optional buffer value + */ + bufferValue?: number; + /** + * The direction that progress bars fill in + * @default 'ltr' + */ + direction?: ProgressDirection; + /** + * Accepts a function which returns a string value that provides an accessible name for the Indicator component + * @param {number | null} value The component's value + * @returns {string} + */ + getAriaLabel?: (index: number | null) => string; + /** + * Accepts a function which returns a string value that provides a human-readable text alternative for the current value of the progress indicator. + * @param {number | null} value The component's value to format + * @returns {string} + */ + getAriaValueText?: (value: number | null, bufferValue: number | undefined) => string; + /** + * The maximum value + * @default 100 + */ + max?: number; + /** + * The minimum value + * @default 0 + */ + min?: number; + /** + * The current value. The component is indeterminate when value is `null`. + * @default null + */ + value: number | null; +} + +export interface UseProgressRootReturnValue { + getRootProps: ( + externalProps?: React.ComponentPropsWithRef<'div'>, + ) => React.ComponentPropsWithRef<'div'>; + /** + * The direction that progress bars fill in + */ + direction: ProgressDirection; + /** + * The maximum value + */ + max: number; + /** + * The minimum value + */ + min: number; + /** + * Value of the buffer + */ + bufferValue?: number; + /** + * Value of the component + */ + value: number | null; + state: ProgressStatus; +} diff --git a/packages/mui-base/src/Progress/Root/styleHooks.ts b/packages/mui-base/src/Progress/Root/styleHooks.ts new file mode 100644 index 0000000000..808c29c561 --- /dev/null +++ b/packages/mui-base/src/Progress/Root/styleHooks.ts @@ -0,0 +1,8 @@ +import type { CustomStyleHookMapping } from '../../utils/getStyleHookProps'; +import type { ProgressRootOwnerState } from './ProgressRoot.types'; + +export const progressStyleHookMapping: CustomStyleHookMapping = { + direction: () => null, + max: () => null, + min: () => null, +}; diff --git a/packages/mui-base/src/Progress/Root/useProgressRoot.ts b/packages/mui-base/src/Progress/Root/useProgressRoot.ts new file mode 100644 index 0000000000..3bc54b7486 --- /dev/null +++ b/packages/mui-base/src/Progress/Root/useProgressRoot.ts @@ -0,0 +1,84 @@ +'use client'; +import * as React from 'react'; +import { mergeReactProps } from '../../utils/mergeReactProps'; +import { + ProgressStatus, + UseProgressRootParameters, + UseProgressRootReturnValue, +} from './ProgressRoot.types'; + +function getDefaultAriaValueText(value: number | null) { + if (value === null) { + return 'indeterminate progress'; + } + + return `${value}%`; +} /** + * + * Demos: + * + * - [Progress](https://mui.com/base-ui/react-progress/#hooks) + * + * API: + * + * - [useProgressRoot API](https://mui.com/base-ui/react-progress/hooks-api/#use-progress-root) + */ +function useProgressRoot(parameters: UseProgressRootParameters): UseProgressRootReturnValue { + const { + 'aria-label': ariaLabel, + 'aria-labelledby': ariaLabelledby, + 'aria-valuetext': ariaValuetext, + bufferValue, + direction = 'ltr', + getAriaLabel, + getAriaValueText, + max = 100, + min = 0, + value, + } = parameters; + + let state: ProgressStatus = 'indeterminate'; + if (Number.isFinite(value)) { + state = value === max ? 'complete' : 'loading'; + } + + const getRootProps: UseProgressRootReturnValue['getRootProps'] = React.useCallback( + (externalProps = {}) => + mergeReactProps<'div'>(externalProps, { + 'aria-label': getAriaLabel ? getAriaLabel(value) : ariaLabel, + 'aria-labelledby': ariaLabelledby, + 'aria-valuemax': bufferValue ? Math.min(bufferValue, max) : max, + 'aria-valuemin': min, + 'aria-valuenow': value ?? undefined, + 'aria-valuetext': getAriaValueText + ? getAriaValueText(value, bufferValue) + : ariaValuetext ?? getDefaultAriaValueText(value), + dir: direction, + role: 'progressbar', + }), + [ + ariaLabel, + ariaLabelledby, + ariaValuetext, + bufferValue, + direction, + getAriaLabel, + getAriaValueText, + max, + min, + value, + ], + ); + + return { + getRootProps, + direction, + max, + min, + bufferValue, + value, + state, + }; +} + +export { useProgressRoot }; diff --git a/packages/mui-base/src/Progress/Track/ProgressTrack.tsx b/packages/mui-base/src/Progress/Track/ProgressTrack.tsx new file mode 100644 index 0000000000..c18036eae8 --- /dev/null +++ b/packages/mui-base/src/Progress/Track/ProgressTrack.tsx @@ -0,0 +1,48 @@ +'use client'; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { useComponentRenderer } from '../../utils/useComponentRenderer'; +import { useProgressContext } from '../Root/ProgressContext'; +import { progressStyleHookMapping } from '../Root/styleHooks'; +import { ProgressTrackProps } from './ProgressTrack.types'; + +const ProgressTrack = React.forwardRef(function ProgressTrack( + props: ProgressTrackProps, + forwardedRef: React.ForwardedRef, +) { + const { render, className, ...otherProps } = props; + + const { ownerState } = useProgressContext(); + + const { renderElement } = useComponentRenderer({ + render: render ?? 'span', + ownerState, + className, + ref: forwardedRef, + extraProps: otherProps, + customStyleHookMapping: progressStyleHookMapping, + }); + + return renderElement(); +}); + +ProgressTrack.propTypes /* remove-proptypes */ = { + // ┌────────────────────────────── Warning ──────────────────────────────┐ + // │ These PropTypes are generated from the TypeScript type definitions. │ + // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ + // └─────────────────────────────────────────────────────────────────────┘ + /** + * @ignore + */ + children: PropTypes.node, + /** + * Class names applied to the element or a function that returns them based on the component's state. + */ + className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + /** + * A function to customize rendering of the component. + */ + render: PropTypes.oneOfType([PropTypes.element, PropTypes.func]), +} as any; + +export { ProgressTrack }; diff --git a/packages/mui-base/src/Progress/Track/ProgressTrack.types.ts b/packages/mui-base/src/Progress/Track/ProgressTrack.types.ts new file mode 100644 index 0000000000..3700c4dfbe --- /dev/null +++ b/packages/mui-base/src/Progress/Track/ProgressTrack.types.ts @@ -0,0 +1,4 @@ +import { BaseUIComponentProps } from '../../utils/types'; +import { ProgressRootOwnerState } from '../Root/ProgressRoot.types'; + +export interface ProgressTrackProps extends BaseUIComponentProps<'span', ProgressRootOwnerState> {} diff --git a/packages/mui-base/src/Progress/index.barrel.ts b/packages/mui-base/src/Progress/index.barrel.ts new file mode 100644 index 0000000000..4c9ca4f7bd --- /dev/null +++ b/packages/mui-base/src/Progress/index.barrel.ts @@ -0,0 +1,23 @@ +export { ProgressRoot } from './Root/ProgressRoot'; +export type * from './Root/ProgressRoot.types'; +export { useProgressRoot } from './Root/useProgressRoot'; +export * from './Root/ProgressContext'; + +export { ProgressTrack } from './Track/ProgressTrack'; +export type { ProgressTrackProps } from './Track/ProgressTrack.types'; + +export { ProgressIndicator } from './Indicator/ProgressIndicator'; +export type { + ProgressIndicatorProps, + UseProgressIndicatorParameters, + UseProgressIndicatorReturnValue, +} from './Indicator/ProgressIndicator.types'; +export { useProgressIndicator } from './Indicator/useProgressIndicator'; + +export { ProgressBuffer } from './Buffer/ProgressBuffer'; +export type { + ProgressBufferProps, + UseProgressBufferParameters, + UseProgressBufferReturnValue, +} from './Buffer/ProgressBuffer.types'; +export { useProgressBuffer } from './Buffer/useProgressBuffer'; diff --git a/packages/mui-base/src/Progress/index.ts b/packages/mui-base/src/Progress/index.ts new file mode 100644 index 0000000000..1a2695b45f --- /dev/null +++ b/packages/mui-base/src/Progress/index.ts @@ -0,0 +1,29 @@ +export { ProgressRoot as Root } from './Root/ProgressRoot'; +export { + ProgressRootOwnerState as ProgressOwnerState, + ProgressRootProps as RootProps, + UseProgressRootParameters, + UseProgressRootReturnValue, + ProgressContextValue, +} from './Root/ProgressRoot.types'; +export { useProgressRoot } from './Root/useProgressRoot'; +export * from './Root/ProgressContext'; + +export { ProgressTrack as Track } from './Track/ProgressTrack'; +export type { ProgressTrackProps as TrackProps } from './Track/ProgressTrack.types'; + +export { ProgressIndicator as Indicator } from './Indicator/ProgressIndicator'; +export type { + ProgressIndicatorProps as IndicatorProps, + UseProgressIndicatorParameters, + UseProgressIndicatorReturnValue, +} from './Indicator/ProgressIndicator.types'; +export { useProgressIndicator } from './Indicator/useProgressIndicator'; + +export { ProgressBuffer as Buffer } from './Buffer/ProgressBuffer'; +export type { + ProgressBufferProps as BufferProps, + UseProgressBufferParameters, + UseProgressBufferReturnValue, +} from './Buffer/ProgressBuffer.types'; +export { useProgressBuffer } from './Buffer/useProgressBuffer'; diff --git a/packages/mui-base/src/index.ts b/packages/mui-base/src/index.ts index 9a78cfba56..7f29893f01 100644 --- a/packages/mui-base/src/index.ts +++ b/packages/mui-base/src/index.ts @@ -3,6 +3,7 @@ export * from './Checkbox/index.barrel'; export * from './Dialog/index.barrel'; export * from './NumberField/index.barrel'; export * from './Popover/index.barrel'; +export * from './Progress/index.barrel'; export * from './Slider/index.barrel'; export * from './Switch/index.barrel'; export * from './Tabs/index.barrel';