diff --git a/.eslintrc.js b/.eslintrc.js
index 051f32dd561433..9833425ea86db7 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -64,8 +64,7 @@ module.exports = {
'!@material-ui/utils/macros',
'@material-ui/utils/macros/*',
'!@material-ui/utils/macros/*.macro',
- // public API: https://next.material-ui-pickers.dev/getting-started/installation#peer-library
- '!@material-ui/pickers/adapter/*',
+ '!@material-ui/lab/dateAdapter/*',
],
},
],
diff --git a/.gitignore b/.gitignore
index 0f2a98601f9ae1..adcfc918790f40 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@
.idea
.vscode
*.log
+*.tsbuildinfo
/.eslintcache
/.nyc_output
/benchmark/**/dist
diff --git a/README.md b/README.md
index 480d2d64ba1805..191fee151e8220 100644
--- a/README.md
+++ b/README.md
@@ -1,178 +1 @@
-
-
-
-
-Material-UI
-
-
-
-[React](https://reactjs.org/) components for faster and simpler web development. Build your own design system, or start with [Material Design](https://material.io/design/introduction/).
-
-[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/mui-org/material-ui/blob/master/LICENSE)
-[![npm latest package](https://img.shields.io/npm/v/@material-ui/core/latest.svg)](https://www.npmjs.com/package/@material-ui/core)
-[![npm next package](https://img.shields.io/npm/v/@material-ui/core/next.svg)](https://www.npmjs.com/package/@material-ui/core)
-[![npm downloads](https://img.shields.io/npm/dm/@material-ui/core.svg)](https://www.npmjs.com/package/@material-ui/core)
-[![CircleCI](https://img.shields.io/circleci/project/github/mui-org/material-ui/next.svg)](https://app.circleci.com/pipelines/github/mui-org/material-ui?branch=next)
-[![Coverage Status](https://img.shields.io/codecov/c/github/mui-org/material-ui/next.svg)](https://codecov.io/gh/mui-org/material-ui/branch/next)
-[![Follow on Twitter](https://img.shields.io/twitter/follow/MaterialUI.svg?label=follow+Material-UI)](https://twitter.com/MaterialUI)
-[![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=mui-org/material-ui)](https://dependabot.com)
-[![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/mui-org/material-ui.svg)](https://isitmaintained.com/project/mui-org/material-ui 'Average time to resolve an issue')
-[![Crowdin](https://badges.crowdin.net/material-ui-docs/localized.svg)](https://translate.material-ui.com/project/material-ui-docs)
-[![Open Collective backers and sponsors](https://img.shields.io/opencollective/all/material-ui)](https://opencollective.com/material-ui)
-
-
-
-## Installation
-
-Material-UI is available as an [npm package](https://www.npmjs.com/package/@material-ui/core).
-
-**[Stable channel v4](https://material-ui.com/)**
-
-```sh
-// with npm
-npm install @material-ui/core
-
-// with yarn
-yarn add @material-ui/core
-```
-
-**[Alpha channel v5](https://next.material-ui.com/)**
-
-```sh
-// with npm
-npm install @material-ui/core@next
-
-// with yarn
-yarn add @material-ui/core@next
-```
-
-
- Older versions
-
- - **[v3.x](https://v3.material-ui.com/)** ([Migration from v3 to v4](https://material-ui.com/guides/migration-v3/))
- - **[v0.x](https://v0.material-ui.com/)** ([Migration to v1](https://material-ui.com/guides/migration-v0x/))
-
-
-
-Please note that `@next` will only point to pre-releases; to get the latest stable release use `@latest` instead.
-
-## Who sponsors Material-UI?
-
-### Diamond 💎
-
-
-
-
-
-
-Diamond Sponsors are those who have pledged \$1,500/month or more to Material-UI.
-
-### Gold 🏆
-
-via [Patreon](https://www.patreon.com/oliviertassinari)
-
-
-
-
-
-
-via [OpenCollective](https://opencollective.com/material-ui)
-
-
-
-
-
-
-Gold Sponsors are those who have pledged \$500/month or more to Material-UI.
-
-### There is more!
-
-See the full list of [our backers](https://material-ui.com/discover-more/backers/).
-
-## Usage
-
-Here is a quick example to get you started, **it's all you need**:
-
-```jsx
-import React from 'react';
-import ReactDOM from 'react-dom';
-import Button from '@material-ui/core/Button';
-
-function App() {
- return Hello World ;
-}
-
-ReactDOM.render( , document.querySelector('#app'));
-```
-
-Yes, it's really all you need to get started as you can see in this live and interactive demo:
-
-[![Edit Button](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/4j7m47vlm4)
-
-## Questions
-
-For _how-to_ questions and other non-issues,
-please use [StackOverflow](https://stackoverflow.com/questions/tagged/material-ui) instead of GitHub issues.
-There is a StackOverflow tag called "material-ui" that you can use to tag your questions.
-
-## Examples
-
-Are you looking for an example project to get started?
-[We host some](https://material-ui.com/getting-started/example-projects/).
-
-## Documentation
-
-Check out our [documentation website](https://material-ui.com/).
-
-## Premium Themes
-
-You can find complete templates & themes in the [Material-UI store](https://material-ui.com/store/?utm_source=docs&utm_medium=referral&utm_campaign=readme-store).
-
-## Contributing
-
-Read our [contributing guide](/CONTRIBUTING.md) to learn about our development process, how to propose bugfixes and improvements, and how to build and test your changes to Material-UI.
-
-Notice that contributions go far beyond pull requests and commits.
-Although we love giving you the opportunity to put your stamp on Material-UI, we also are thrilled to receive a variety of [other contributions](https://material-ui.com/getting-started/faq/#material-ui-is-awesome-how-can-i-support-the-project).
-
-## Changelog
-
-Recently Updated?
-Please read the [changelog](https://github.com/mui-org/material-ui/releases).
-
-## Roadmap
-
-The future plans and high priority features and enhancements can be found in the [roadmap](https://material-ui.com/discover-more/roadmap/) file.
-
-## License
-
-This project is licensed under the terms of the
-[MIT license](/LICENSE).
-
-## Sponsoring services
-
-These great services sponsor Material-UI's core infrastructure:
-
-[ ](https://github.com/)
-
-GitHub allows us to host the Git repository.
-
-[ ](https://circleci.com/)
-
-CircleCI allows us to run the test suite.
-
-[ ](https://www.netlify.com/)
-
-Netlify allows us to distribute the documentation.
-
-[ ](https://crowdin.com/)
-
-CrowdIn allows us to translate the documentation.
-
-[ ](https://www.browserstack.com/)
-
-BrowserStack allows us to test in real browsers.
-
-[ ](https://codecov.io/)
-
-CodeCov allows us to monitor the test coverage.
+Temporary repository to merge the date picker components in main codebase.
diff --git a/docs/next.config.js b/docs/next.config.js
index cc54352f5913df..9e87a4a71ea879 100644
--- a/docs/next.config.js
+++ b/docs/next.config.js
@@ -71,9 +71,7 @@ module.exports = {
config.externals = [
(context, request, callback) => {
- const hasDependencyOnRepoPackages = ['notistack', '@material-ui/pickers'].includes(
- request,
- );
+ const hasDependencyOnRepoPackages = ['notistack'].includes(request);
if (hasDependencyOnRepoPackages) {
return callback(null);
@@ -108,7 +106,7 @@ module.exports = {
// transpile 3rd party packages with dependencies in this repository
{
test: /\.(js|mjs|jsx)$/,
- include: /node_modules(\/|\\)(notistack|@material-ui(\/|\\)pickers)/,
+ include: /node_modules(\/|\\)notistack/,
use: {
loader: 'babel-loader',
options: {
diff --git a/docs/package.json b/docs/package.json
index 7d41fd91ffba4b..4a46abb92774db 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -32,7 +32,6 @@
"@material-ui/docs": "^5.0.0-alpha.1",
"@material-ui/icons": "^5.0.0-alpha.1",
"@material-ui/lab": "^5.0.0-alpha.1",
- "@material-ui/pickers": "^4.0.0-alpha.11",
"@material-ui/styled-engine": "^5.0.0-alpha.1",
"@material-ui/styled-engine-sc": "^5.0.0-alpha.1",
"@material-ui/styles": "^5.0.0-alpha.1",
@@ -69,7 +68,7 @@
"create-emotion-server": "^10.0.27",
"cross-env": "^7.0.0",
"css-mediaquery": "^0.1.2",
- "date-fns": "^2.15.0",
+ "date-fns": "^2.0.0",
"docsearch.js": "^2.6.3",
"doctrine": "^3.0.0",
"emotion-theming": "^10.0.27",
diff --git a/docs/pages/components/date-picker.js b/docs/pages/components/date-picker.js
new file mode 100644
index 00000000000000..a9faa5bc5db5fb
--- /dev/null
+++ b/docs/pages/components/date-picker.js
@@ -0,0 +1,24 @@
+import React from 'react';
+import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
+import { prepareMarkdown } from 'docs/src/modules/utils/parseMarkdown';
+
+const pageFilename = 'components/date-picker';
+const requireDemo = require.context('docs/src/pages/components/date-picker', false, /\.(js|tsx)$/);
+const requireRaw = require.context(
+ '!raw-loader!../../src/pages/components/date-picker',
+ false,
+ /\.(js|md|tsx)$/,
+);
+
+// Run styled-components ref logic
+// https://github.com/styled-components/styled-components/pull/2998
+requireDemo.keys().map(requireDemo);
+
+export default function Page({ demos, docs }) {
+ return ;
+}
+
+Page.getInitialProps = () => {
+ const { demos, docs } = prepareMarkdown({ pageFilename, requireRaw });
+ return { demos, docs };
+};
diff --git a/docs/pages/components/date-range-picker.js b/docs/pages/components/date-range-picker.js
new file mode 100644
index 00000000000000..fdea211ea31c9d
--- /dev/null
+++ b/docs/pages/components/date-range-picker.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
+import { prepareMarkdown } from 'docs/src/modules/utils/parseMarkdown';
+
+const pageFilename = 'components/date-range-picker';
+const requireDemo = require.context(
+ 'docs/src/pages/components/date-range-picker',
+ false,
+ /\.(js|tsx)$/,
+);
+const requireRaw = require.context(
+ '!raw-loader!../../src/pages/components/date-range-picker',
+ false,
+ /\.(js|md|tsx)$/,
+);
+
+// Run styled-components ref logic
+// https://github.com/styled-components/styled-components/pull/2998
+requireDemo.keys().map(requireDemo);
+
+export default function Page({ demos, docs }) {
+ return ;
+}
+
+Page.getInitialProps = () => {
+ const { demos, docs } = prepareMarkdown({ pageFilename, requireRaw });
+ return { demos, docs };
+};
diff --git a/docs/pages/components/date-time-picker.js b/docs/pages/components/date-time-picker.js
new file mode 100644
index 00000000000000..d76dfa6060f970
--- /dev/null
+++ b/docs/pages/components/date-time-picker.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
+import { prepareMarkdown } from 'docs/src/modules/utils/parseMarkdown';
+
+const pageFilename = 'components/date-time-picker';
+const requireDemo = require.context(
+ 'docs/src/pages/components/date-time-picker',
+ false,
+ /\.(js|tsx)$/,
+);
+const requireRaw = require.context(
+ '!raw-loader!../../src/pages/components/date-time-picker',
+ false,
+ /\.(js|md|tsx)$/,
+);
+
+// Run styled-components ref logic
+// https://github.com/styled-components/styled-components/pull/2998
+requireDemo.keys().map(requireDemo);
+
+export default function Page({ demos, docs }) {
+ return ;
+}
+
+Page.getInitialProps = () => {
+ const { demos, docs } = prepareMarkdown({ pageFilename, requireRaw });
+ return { demos, docs };
+};
diff --git a/docs/pages/components/time-picker.js b/docs/pages/components/time-picker.js
new file mode 100644
index 00000000000000..c474df51e50e68
--- /dev/null
+++ b/docs/pages/components/time-picker.js
@@ -0,0 +1,24 @@
+import React from 'react';
+import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
+import { prepareMarkdown } from 'docs/src/modules/utils/parseMarkdown';
+
+const pageFilename = 'components/time-picker';
+const requireDemo = require.context('docs/src/pages/components/time-picker', false, /\.(js|tsx)$/);
+const requireRaw = require.context(
+ '!raw-loader!../../src/pages/components/time-picker',
+ false,
+ /\.(js|md|tsx)$/,
+);
+
+// Run styled-components ref logic
+// https://github.com/styled-components/styled-components/pull/2998
+requireDemo.keys().map(requireDemo);
+
+export default function Page({ demos, docs }) {
+ return ;
+}
+
+Page.getInitialProps = () => {
+ const { demos, docs } = prepareMarkdown({ pageFilename, requireRaw });
+ return { demos, docs };
+};
diff --git a/docs/scripts/buildApi.ts b/docs/scripts/buildApi.ts
index d1d57b32b128af..b54cef27522f61 100644
--- a/docs/scripts/buildApi.ts
+++ b/docs/scripts/buildApi.ts
@@ -281,6 +281,10 @@ async function buildDocs(options: {
prettierConfigPath,
theme,
} = options;
+ if (componentObject.filename.indexOf('internal') !== -1) {
+ return;
+ }
+
const src = readFileSync(componentObject.filename, 'utf8');
if (src.match(/@ignore - internal component\./) || src.match(/@ignore - do not document\./)) {
diff --git a/docs/src/modules/utils/helpers.js b/docs/src/modules/utils/helpers.js
index ca337f20c312e3..5914d7bdd1f44c 100644
--- a/docs/src/modules/utils/helpers.js
+++ b/docs/src/modules/utils/helpers.js
@@ -83,7 +83,6 @@ function includePeerDependencies(deps, versions) {
if (
deps['@material-ui/lab'] ||
- deps['@material-ui/pickers'] ||
deps['@material-ui/x'] ||
deps['@material-ui/x-grid'] ||
deps['@material-ui/x-pickers'] ||
@@ -98,10 +97,6 @@ function includePeerDependencies(deps, versions) {
deps['@material-ui/icons'] = versions['@material-ui/icons'];
deps['@material-ui/lab'] = versions['@material-ui/lab'];
}
-
- if (deps['@material-ui/pickers']) {
- deps['date-fns'] = 'latest';
- }
}
/**
@@ -131,8 +126,10 @@ function getDependencies(raw, options = {}) {
const deps = {};
const versions = {
- 'react-dom': reactVersion,
react: reactVersion,
+ 'react-dom': reactVersion,
+ '@emotion/core': 'latest',
+ '@emotion/styled': 'latest',
'@material-ui/core': getMuiPackageVersion('core', muiCommitRef),
'@material-ui/icons': getMuiPackageVersion('icons', muiCommitRef),
'@material-ui/lab': getMuiPackageVersion('lab', muiCommitRef),
@@ -142,9 +139,6 @@ function getDependencies(raw, options = {}) {
'@material-ui/system': getMuiPackageVersion('system', muiCommitRef),
'@material-ui/unstyled': getMuiPackageVersion('unstyled', muiCommitRef),
'@material-ui/utils': getMuiPackageVersion('utils', muiCommitRef),
- '@material-ui/pickers': 'next',
- '@emotion/core': 'latest',
- '@emotion/styled': 'latest',
};
const re = /^import\s'([^']+)'|import\s[\s\S]*?\sfrom\s+'([^']+)/gm;
@@ -164,6 +158,12 @@ function getDependencies(raw, options = {}) {
if (!deps[name]) {
deps[name] = versions[name] ? versions[name] : 'latest';
}
+
+ // e.g date-fns
+ const dateAdapter = /^@material-ui\/lab\/dateAdapter\/(.*)/;
+ if (dateAdapter.test(m[2])) {
+ deps[dateAdapter.exec(m[2])[1]] = 'latest';
+ }
}
includePeerDependencies(deps, versions);
diff --git a/docs/src/modules/utils/helpers.test.js b/docs/src/modules/utils/helpers.test.js
index 80152e4c1918f4..71cda164298c27 100644
--- a/docs/src/modules/utils/helpers.test.js
+++ b/docs/src/modules/utils/helpers.test.js
@@ -22,13 +22,13 @@ const styles = theme => ({
it('should handle @ dependencies', () => {
expect(getDependencies(s1)).to.deep.equal({
+ react: 'latest',
+ 'react-dom': 'latest',
'@emotion/core': 'latest',
'@emotion/styled': 'latest',
'@foo-bar/bip': 'latest',
'@material-ui/core': 'next',
'prop-types': 'latest',
- 'react-dom': 'latest',
- react: 'latest',
});
});
@@ -48,6 +48,8 @@ const suggestions = [
`;
expect(getDependencies(source)).to.deep.equal({
+ react: 'latest',
+ 'react-dom': 'latest',
'@emotion/core': 'latest',
'@emotion/styled': 'latest',
'@material-ui/core': 'next',
@@ -55,20 +57,18 @@ const suggestions = [
'autosuggest-highlight': 'latest',
'prop-types': 'latest',
'react-draggable': 'latest',
- 'react-dom': 'latest',
- react: 'latest',
});
});
it('should support next dependencies', () => {
expect(getDependencies(s1, { reactVersion: 'next' })).to.deep.equal({
+ react: 'next',
+ 'react-dom': 'next',
'@emotion/core': 'latest',
'@emotion/styled': 'latest',
'@foo-bar/bip': 'latest',
'@material-ui/core': 'next',
'prop-types': 'latest',
- 'react-dom': 'next',
- react: 'next',
});
});
@@ -78,31 +78,31 @@ import * as React from 'react';
import PropTypes from 'prop-types';
import Grid from '@material-ui/core/Grid';
import { withStyles } from '@material-ui/core/styles';
-import DateFnsAdapter from "@material-ui/pickers/adapter/date-fns";
-import { LocalizationProvider as MuiPickersLocalizationProvider, KeyboardTimePicker, KeyboardDatePicker } from '@material-ui/pickers';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import { LocalizationProvider as MuiPickersLocalizationProvider, KeyboardTimePicker, KeyboardDatePicker } from '@material-ui/lab';
`;
expect(getDependencies(source)).to.deep.equal({
- 'date-fns': 'latest',
+ react: 'latest',
+ 'react-dom': 'latest',
+ 'prop-types': 'latest',
'@emotion/core': 'latest',
'@emotion/styled': 'latest',
- '@material-ui/pickers': 'next',
'@material-ui/core': 'next',
- 'prop-types': 'latest',
- 'react-dom': 'latest',
- react: 'latest',
+ '@material-ui/lab': 'next',
+ 'date-fns': 'latest',
});
});
it('can collect required @types packages', () => {
expect(getDependencies(s1, { codeLanguage: 'TS' })).to.deep.equal({
+ react: 'latest',
+ 'react-dom': 'latest',
+ 'prop-types': 'latest',
'@emotion/core': 'latest',
'@emotion/styled': 'latest',
'@foo-bar/bip': 'latest',
'@material-ui/core': 'next',
- 'prop-types': 'latest',
- 'react-dom': 'latest',
- react: 'latest',
'@types/foo-bar__bip': 'latest',
'@types/prop-types': 'latest',
'@types/react-dom': 'latest',
@@ -114,22 +114,22 @@ import { LocalizationProvider as MuiPickersLocalizationProvider, KeyboardTimePic
it('should handle multilines', () => {
const source = `
import * as React from 'react';
-import DateFnsAdapter from '@material-ui/pickers/adapter/date-fns';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
import {
LocalizationProvider as MuiPickersLocalizationProvider,
KeyboardTimePicker,
KeyboardDatePicker,
-} from '@material-ui/pickers';
+} from '@material-ui/lab';
`;
expect(getDependencies(source)).to.deep.equal({
- 'date-fns': 'latest',
+ react: 'latest',
+ 'react-dom': 'latest',
'@emotion/core': 'latest',
'@emotion/styled': 'latest',
'@material-ui/core': 'next',
- '@material-ui/pickers': 'next',
- react: 'latest',
- 'react-dom': 'latest',
+ '@material-ui/lab': 'next',
+ 'date-fns': 'latest',
});
});
@@ -139,12 +139,12 @@ import lab from '@material-ui/lab';
`;
expect(getDependencies(source)).to.deep.equal({
+ react: 'latest',
+ 'react-dom': 'latest',
'@emotion/core': 'latest',
'@emotion/styled': 'latest',
'@material-ui/core': 'next',
'@material-ui/lab': 'next',
- react: 'latest',
- 'react-dom': 'latest',
});
});
@@ -156,6 +156,8 @@ import { useDemoData } from '@material-ui/x-grid-data-generator';
`;
expect(getDependencies(source, { codeLanguage: 'TS' })).to.deep.equal({
+ react: 'latest',
+ 'react-dom': 'latest',
'@emotion/core': 'latest',
'@emotion/styled': 'latest',
'@material-ui/core': 'next',
@@ -165,8 +167,6 @@ import { useDemoData } from '@material-ui/x-grid-data-generator';
'@material-ui/x-grid-data-generator': 'latest',
'@types/react': 'latest',
'@types/react-dom': 'latest',
- react: 'latest',
- 'react-dom': 'latest',
typescript: 'latest',
});
});
diff --git a/docs/src/pages.js b/docs/src/pages.js
index 0abcbefb7047d5..dd1b74621197f9 100644
--- a/docs/src/pages.js
+++ b/docs/src/pages.js
@@ -38,7 +38,6 @@ const pages = [
{ pathname: '/components/button-group' },
{ pathname: '/components/checkboxes', title: 'Checkbox' },
{ pathname: '/components/floating-action-button' },
- { pathname: '/components/pickers', title: 'Date / Time' },
{ pathname: '/components/radio-buttons', title: 'Radio button' },
{ pathname: '/components/rating' },
{ pathname: '/components/selects', title: 'Select' },
@@ -145,6 +144,18 @@ const pages = [
subheader: '/components/lab',
children: [
{ pathname: '/components/about-the-lab', title: 'About the lab 🧪' },
+ {
+ pathname: '/components',
+ subheader: '/components/lab-pickers',
+ title: 'Date / Time',
+ children: [
+ { pathname: '/components/pickers', title: 'Introduction' },
+ { pathname: '/components/date-picker' },
+ { pathname: '/components/date-range-picker' },
+ { pathname: '/components/date-time-picker' },
+ { pathname: '/components/time-picker' },
+ ],
+ },
{ pathname: '/components/slider-styled' },
{ pathname: '/components/timeline' },
{ pathname: '/components/trap-focus' },
diff --git a/docs/src/pages/components/date-picker/BasicDatePicker.js b/docs/src/pages/components/date-picker/BasicDatePicker.js
new file mode 100644
index 00000000000000..264282a26d1890
--- /dev/null
+++ b/docs/src/pages/components/date-picker/BasicDatePicker.js
@@ -0,0 +1,22 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DatePicker from '@material-ui/lab/DatePicker';
+
+export default function BasicDatePicker() {
+ const [value, setValue] = React.useState(null);
+
+ return (
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+
+ );
+}
diff --git a/docs/src/pages/components/date-picker/BasicDatePicker.tsx b/docs/src/pages/components/date-picker/BasicDatePicker.tsx
new file mode 100644
index 00000000000000..40ad4418a997c4
--- /dev/null
+++ b/docs/src/pages/components/date-picker/BasicDatePicker.tsx
@@ -0,0 +1,22 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DatePicker from '@material-ui/lab/DatePicker';
+
+export default function BasicDatePicker() {
+ const [value, setValue] = React.useState(null);
+
+ return (
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+
+ );
+}
diff --git a/docs/src/pages/components/date-picker/CustomDay.js b/docs/src/pages/components/date-picker/CustomDay.js
new file mode 100644
index 00000000000000..a4007731f04b27
--- /dev/null
+++ b/docs/src/pages/components/date-picker/CustomDay.js
@@ -0,0 +1,78 @@
+import * as React from 'react';
+import { makeStyles } from '@material-ui/core';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizaitonProvider from '@material-ui/lab/LocalizationProvider';
+import DatePicker from '@material-ui/lab/DatePicker';
+import PickersDay from '@material-ui/lab/PickersDay';
+import clsx from 'clsx';
+import endOfWeek from 'date-fns/endOfWeek';
+import isSameDay from 'date-fns/isSameDay';
+import isWithinInterval from 'date-fns/isWithinInterval';
+import startOfWeek from 'date-fns/startOfWeek';
+
+const useStyles = makeStyles((theme) => ({
+ highlight: {
+ borderRadius: 0,
+ backgroundColor: theme.palette.primary.main,
+ color: theme.palette.common.white,
+ '&:hover, &:focus': {
+ backgroundColor: theme.palette.primary.dark,
+ },
+ },
+ firstHighlight: {
+ borderTopLeftRadius: '50%',
+ borderBottomLeftRadius: '50%',
+ },
+ endHighlight: {
+ borderTopRightRadius: '50%',
+ borderBottomRightRadius: '50%',
+ },
+}));
+
+export default function CustomDay() {
+ const classes = useStyles();
+ const [selectedDate, handleDateChange] = React.useState(new Date());
+
+ const renderWeekPickerDay = (
+ date,
+ _selectedDates,
+ PickersDayComponentProps,
+ ) => {
+ if (!selectedDate) {
+ return ;
+ }
+
+ const start = startOfWeek(selectedDate);
+ const end = endOfWeek(selectedDate);
+
+ const dayIsBetween = isWithinInterval(date, { start, end });
+ const isFirstDay = isSameDay(date, start);
+ const isLastDay = isSameDay(date, end);
+
+ return (
+
+ );
+ };
+
+ return (
+
+ }
+ inputFormat="'Week of' MMM d"
+ />
+
+ );
+}
diff --git a/packages/pickers/docs/pages/demo/datepicker/CustomDay.example.tsx b/docs/src/pages/components/date-picker/CustomDay.tsx
similarity index 50%
rename from packages/pickers/docs/pages/demo/datepicker/CustomDay.example.tsx
rename to docs/src/pages/components/date-picker/CustomDay.tsx
index 47bd1ea269efc1..7717868d0e8ec2 100644
--- a/packages/pickers/docs/pages/demo/datepicker/CustomDay.example.tsx
+++ b/docs/src/pages/components/date-picker/CustomDay.tsx
@@ -1,16 +1,15 @@
-/* eslint-disable no-underscore-dangle */
import * as React from 'react';
+import { makeStyles } from '@material-ui/core';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizaitonProvider from '@material-ui/lab/LocalizationProvider';
+import DatePicker from '@material-ui/lab/DatePicker';
+import PickersDay, { PickersDayProps } from '@material-ui/lab/PickersDay';
import clsx from 'clsx';
-import isSameDay from 'date-fns/isSameDay';
import endOfWeek from 'date-fns/endOfWeek';
-import startOfWeek from 'date-fns/startOfWeek';
-import TextField from '@material-ui/core/TextField';
+import isSameDay from 'date-fns/isSameDay';
import isWithinInterval from 'date-fns/isWithinInterval';
-import { makeStyles } from '@material-ui/core';
-// this guy required only on the docs site to work with dynamic date library
-import { DatePicker, PickersDay, PickersDayProps } from '@material-ui/pickers';
-// TODO remove relative import
-import { makeJSDateObject } from '../../../utils/helpers';
+import startOfWeek from 'date-fns/startOfWeek';
const useStyles = makeStyles((theme) => ({
highlight: {
@@ -31,28 +30,31 @@ const useStyles = makeStyles((theme) => ({
},
}));
-export default function CustomDay(demoProps: any) {
+export default function CustomDay() {
const classes = useStyles();
- const [selectedDate, handleDateChange] = React.useState(new Date());
+ const [selectedDate, handleDateChange] = React.useState(
+ new Date(),
+ );
const renderWeekPickerDay = (
date: Date,
_selectedDates: Date[],
- DayComponentProps: PickersDayProps
+ PickersDayComponentProps: PickersDayProps,
) => {
- const dateClone = makeJSDateObject(date);
- const selectedDateClone = makeJSDateObject(selectedDate ?? new Date());
+ if (!selectedDate) {
+ return ;
+ }
- const start = startOfWeek(selectedDateClone);
- const end = endOfWeek(selectedDateClone);
+ const start = startOfWeek(selectedDate);
+ const end = endOfWeek(selectedDate);
- const dayIsBetween = isWithinInterval(dateClone, { start, end });
- const isFirstDay = isSameDay(dateClone, start);
- const isLastDay = isSameDay(dateClone, end);
+ const dayIsBetween = isWithinInterval(date, { start, end });
+ const isFirstDay = isSameDay(date, start);
+ const isLastDay = isSameDay(date, end);
return (
}
- inputFormat={demoProps.__willBeReplacedGetFormatString({
- moment: `[Week of] MMM D`,
- dateFns: "'Week of' MMM d",
- })}
- />
+
+ }
+ inputFormat="'Week of' MMM d"
+ />
+
);
}
diff --git a/docs/src/pages/components/date-picker/CustomInput.js b/docs/src/pages/components/date-picker/CustomInput.js
new file mode 100644
index 00000000000000..62f6ec8116f184
--- /dev/null
+++ b/docs/src/pages/components/date-picker/CustomInput.js
@@ -0,0 +1,27 @@
+import * as React from 'react';
+import Box from '@material-ui/core/Box';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizaitonProvider from '@material-ui/lab/LocalizationProvider';
+import DesktopDatePicker from '@material-ui/lab/DatePicker';
+
+export default function CustomInput() {
+ const [value, setValue] = React.useState(new Date());
+
+ return (
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={({ inputRef, inputProps, InputProps }) => (
+
+
+ {InputProps?.endAdornment}
+
+ )}
+ />
+
+ );
+}
diff --git a/docs/src/pages/components/date-picker/CustomInput.tsx b/docs/src/pages/components/date-picker/CustomInput.tsx
new file mode 100644
index 00000000000000..4450f2abe79c54
--- /dev/null
+++ b/docs/src/pages/components/date-picker/CustomInput.tsx
@@ -0,0 +1,27 @@
+import * as React from 'react';
+import Box from '@material-ui/core/Box';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizaitonProvider from '@material-ui/lab/LocalizationProvider';
+import DesktopDatePicker from '@material-ui/lab/DatePicker';
+
+export default function CustomInput() {
+ const [value, setValue] = React.useState(new Date());
+
+ return (
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={({ inputRef, inputProps, InputProps }) => (
+
+
+ {InputProps?.endAdornment}
+
+ )}
+ />
+
+ );
+}
diff --git a/docs/src/pages/components/date-picker/InternalPickers.js b/docs/src/pages/components/date-picker/InternalPickers.js
new file mode 100644
index 00000000000000..dc630d6404d03e
--- /dev/null
+++ b/docs/src/pages/components/date-picker/InternalPickers.js
@@ -0,0 +1,18 @@
+import * as React from 'react';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizaitonProvider from '@material-ui/lab/LocalizationProvider';
+import DayPicker from '@material-ui/lab/DayPicker';
+
+export default function InternalPickers() {
+ const [date, setDate] = React.useState(new Date());
+
+ return (
+
+ setDate(newValue)}
+ />
+
+ );
+}
diff --git a/docs/src/pages/components/date-picker/InternalPickers.tsx b/docs/src/pages/components/date-picker/InternalPickers.tsx
new file mode 100644
index 00000000000000..b2a7f16c48cd0b
--- /dev/null
+++ b/docs/src/pages/components/date-picker/InternalPickers.tsx
@@ -0,0 +1,18 @@
+import * as React from 'react';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizaitonProvider from '@material-ui/lab/LocalizationProvider';
+import DayPicker from '@material-ui/lab/DayPicker';
+
+export default function InternalPickers() {
+ const [date, setDate] = React.useState(new Date());
+
+ return (
+
+ setDate(newValue)}
+ />
+
+ );
+}
diff --git a/docs/src/pages/components/date-picker/LocalizedDatePicker.js b/docs/src/pages/components/date-picker/LocalizedDatePicker.js
new file mode 100644
index 00000000000000..47c9b5eba21a41
--- /dev/null
+++ b/docs/src/pages/components/date-picker/LocalizedDatePicker.js
@@ -0,0 +1,61 @@
+import * as React from 'react';
+import frLocale from 'date-fns/locale/fr';
+import ruLocale from 'date-fns/locale/ru';
+import deLocale from 'date-fns/locale/de';
+import enLocale from 'date-fns/locale/en-US';
+import ToggleButton from '@material-ui/core/ToggleButton';
+import ToggleButtonGroup from '@material-ui/core/ToggleButtonGroup';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import DatePicker from '@material-ui/lab/DatePicker';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+
+const localeMap = {
+ en: enLocale,
+ fr: frLocale,
+ ru: ruLocale,
+ de: deLocale,
+};
+
+const maskMap = {
+ fr: '__/__/____',
+ en: '__/__/____',
+ ru: '__.__.____',
+ de: '__.__.____',
+};
+
+export default function LocalizedDatePicker() {
+ const [locale, setLocale] = React.useState('ru');
+ const [selectedDate, handleDateChange] = React.useState(new Date());
+
+ const selectLocale = (newLocale) => {
+ setLocale(newLocale);
+ };
+
+ return (
+
+
+ handleDateChange(date)}
+ renderInput={(params) => }
+ />
+
+ {Object.keys(localeMap).map((localeItem) => (
+ selectLocale(localeItem)}
+ >
+ {localeItem}
+
+ ))}
+
+
+
+ );
+}
diff --git a/docs/src/pages/components/date-picker/LocalizedDatePicker.tsx b/docs/src/pages/components/date-picker/LocalizedDatePicker.tsx
new file mode 100644
index 00000000000000..d077d0660b1329
--- /dev/null
+++ b/docs/src/pages/components/date-picker/LocalizedDatePicker.tsx
@@ -0,0 +1,63 @@
+import * as React from 'react';
+import frLocale from 'date-fns/locale/fr';
+import ruLocale from 'date-fns/locale/ru';
+import deLocale from 'date-fns/locale/de';
+import enLocale from 'date-fns/locale/en-US';
+import ToggleButton from '@material-ui/core/ToggleButton';
+import ToggleButtonGroup from '@material-ui/core/ToggleButtonGroup';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import DatePicker from '@material-ui/lab/DatePicker';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+
+const localeMap = {
+ en: enLocale,
+ fr: frLocale,
+ ru: ruLocale,
+ de: deLocale,
+};
+
+const maskMap = {
+ fr: '__/__/____',
+ en: '__/__/____',
+ ru: '__.__.____',
+ de: '__.__.____',
+};
+
+export default function LocalizedDatePicker() {
+ const [locale, setLocale] = React.useState('ru');
+ const [selectedDate, handleDateChange] = React.useState(
+ new Date(),
+ );
+
+ const selectLocale = (newLocale: any) => {
+ setLocale(newLocale);
+ };
+
+ return (
+
+
+ handleDateChange(date)}
+ renderInput={(params) => }
+ />
+
+ {Object.keys(localeMap).map((localeItem) => (
+ selectLocale(localeItem)}
+ >
+ {localeItem}
+
+ ))}
+
+
+
+ );
+}
diff --git a/docs/src/pages/components/date-picker/ResponsiveDatePickers.js b/docs/src/pages/components/date-picker/ResponsiveDatePickers.js
new file mode 100644
index 00000000000000..30aa27aac43a08
--- /dev/null
+++ b/docs/src/pages/components/date-picker/ResponsiveDatePickers.js
@@ -0,0 +1,46 @@
+import React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DatePicker from '@material-ui/lab/DatePicker';
+import MobileDatePicker from '@material-ui/lab/MobileDatePicker';
+import DesktopDatePicker from '@material-ui/lab/DesktopDatePicker';
+
+export default function ResponsiveDatePickers() {
+ const [value, setValue] = React.useState(new Date());
+
+ return (
+
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+
+
+ );
+}
diff --git a/docs/src/pages/components/date-picker/ResponsiveDatePickers.tsx b/docs/src/pages/components/date-picker/ResponsiveDatePickers.tsx
new file mode 100644
index 00000000000000..09c17ad9e74d0b
--- /dev/null
+++ b/docs/src/pages/components/date-picker/ResponsiveDatePickers.tsx
@@ -0,0 +1,46 @@
+import React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DatePicker from '@material-ui/lab/DatePicker';
+import MobileDatePicker from '@material-ui/lab/MobileDatePicker';
+import DesktopDatePicker from '@material-ui/lab/DesktopDatePicker';
+
+export default function ResponsiveDatePickers() {
+ const [value, setValue] = React.useState(new Date());
+
+ return (
+
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+
+
+ );
+}
diff --git a/docs/src/pages/components/date-picker/ServerRequestDatePicker.js b/docs/src/pages/components/date-picker/ServerRequestDatePicker.js
new file mode 100644
index 00000000000000..477e6635c62010
--- /dev/null
+++ b/docs/src/pages/components/date-picker/ServerRequestDatePicker.js
@@ -0,0 +1,106 @@
+import * as React from 'react';
+import Badge from '@material-ui/core/Badge';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizaitonProvider from '@material-ui/lab/LocalizationProvider';
+import PickersDay from '@material-ui/lab/PickersDay';
+import DatePicker from '@material-ui/lab/DatePicker';
+import PickersCalendarSkeleton from '@material-ui/lab/PickersCalendarSkeleton';
+import getDaysInMonth from 'date-fns/getDaysInMonth';
+
+function getRandomNumber(min, max) {
+ return Math.round(Math.random() * (max - min) + min);
+}
+
+/**
+ * Mimic fetch with abort controller https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort
+ * ⚠️ No IE11 support
+ */
+function fakeFetch(date, { signal }) {
+ return new Promise((resolve, reject) => {
+ const timeout = setTimeout(() => {
+ const daysInMonth = getDaysInMonth(date);
+ const daysToHighlight = [1, 2, 3].map(() =>
+ getRandomNumber(1, daysInMonth),
+ );
+
+ resolve({ daysToHighlight });
+ }, 500);
+
+ signal.onabort = () => {
+ clearTimeout(timeout);
+ reject(new Error('aborted'));
+ };
+ });
+}
+
+const initialValue = new Date();
+
+export default function ServerRequestDatePicker() {
+ const requestAbortController = React.useRef(null);
+ const [isLoading, setIsLoading] = React.useState(false);
+ const [highlightedDays, setHighlightedDays] = React.useState([1, 2, 15]);
+ const [value, setValue] = React.useState(initialValue);
+
+ const fetchHighlightedDays = (date) => {
+ const controller = new AbortController();
+ fakeFetch(date, {
+ signal: controller.signal,
+ })
+ .then(({ daysToHighlight }) => {
+ setHighlightedDays(daysToHighlight);
+ setIsLoading(false);
+ })
+ .catch(() => console.log('Wow, you are switching months too quickly 🐕'));
+
+ requestAbortController.current = controller;
+ };
+
+ React.useEffect(() => {
+ fetchHighlightedDays(initialValue);
+ // abort request on unmount
+ return () => requestAbortController.current?.abort();
+ }, []);
+
+ const handleMonthChange = (date) => {
+ if (requestAbortController.current) {
+ // make sure that you are aborting useless requests
+ // because it is possible to switch between months pretty quickly
+ requestAbortController.current.abort();
+ }
+
+ setIsLoading(true);
+ setHighlightedDays([]);
+ fetchHighlightedDays(date);
+ };
+
+ return (
+
+ {
+ setValue(newValue);
+ }}
+ onMonthChange={handleMonthChange}
+ renderInput={(params) => }
+ renderLoading={() => }
+ renderDay={(day, _value, DayComponentProps) => {
+ const isSelected =
+ !DayComponentProps.outsideCurrentMonth &&
+ highlightedDays.indexOf(day.getDate()) > 0;
+
+ return (
+
+
+
+ );
+ }}
+ />
+
+ );
+}
diff --git a/docs/src/pages/components/date-picker/ServerRequestDatePicker.tsx b/docs/src/pages/components/date-picker/ServerRequestDatePicker.tsx
new file mode 100644
index 00000000000000..295f98c649cd1b
--- /dev/null
+++ b/docs/src/pages/components/date-picker/ServerRequestDatePicker.tsx
@@ -0,0 +1,106 @@
+import * as React from 'react';
+import Badge from '@material-ui/core/Badge';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizaitonProvider from '@material-ui/lab/LocalizationProvider';
+import PickersDay from '@material-ui/lab/PickersDay';
+import DatePicker from '@material-ui/lab/DatePicker';
+import PickersCalendarSkeleton from '@material-ui/lab/PickersCalendarSkeleton';
+import getDaysInMonth from 'date-fns/getDaysInMonth';
+
+function getRandomNumber(min: number, max: number) {
+ return Math.round(Math.random() * (max - min) + min);
+}
+
+/**
+ * Mimic fetch with abort controller https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort
+ * ⚠️ No IE11 support
+ */
+function fakeFetch(date: Date, { signal }: { signal: AbortSignal }) {
+ return new Promise<{ daysToHighlight: number[] }>((resolve, reject) => {
+ const timeout = setTimeout(() => {
+ const daysInMonth = getDaysInMonth(date);
+ const daysToHighlight = [1, 2, 3].map(() =>
+ getRandomNumber(1, daysInMonth),
+ );
+
+ resolve({ daysToHighlight });
+ }, 500);
+
+ signal.onabort = () => {
+ clearTimeout(timeout);
+ reject(new Error('aborted'));
+ };
+ });
+}
+
+const initialValue = new Date();
+
+export default function ServerRequestDatePicker() {
+ const requestAbortController = React.useRef(null);
+ const [isLoading, setIsLoading] = React.useState(false);
+ const [highlightedDays, setHighlightedDays] = React.useState([1, 2, 15]);
+ const [value, setValue] = React.useState(initialValue);
+
+ const fetchHighlightedDays = (date: Date) => {
+ const controller = new AbortController();
+ fakeFetch(date, {
+ signal: controller.signal,
+ })
+ .then(({ daysToHighlight }) => {
+ setHighlightedDays(daysToHighlight);
+ setIsLoading(false);
+ })
+ .catch(() => console.log('Wow, you are switching months too quickly 🐕'));
+
+ requestAbortController.current = controller;
+ };
+
+ React.useEffect(() => {
+ fetchHighlightedDays(initialValue);
+ // abort request on unmount
+ return () => requestAbortController.current?.abort();
+ }, []);
+
+ const handleMonthChange = (date: Date) => {
+ if (requestAbortController.current) {
+ // make sure that you are aborting useless requests
+ // because it is possible to switch between months pretty quickly
+ requestAbortController.current.abort();
+ }
+
+ setIsLoading(true);
+ setHighlightedDays([]);
+ fetchHighlightedDays(date);
+ };
+
+ return (
+
+ {
+ setValue(newValue);
+ }}
+ onMonthChange={handleMonthChange}
+ renderInput={(params) => }
+ renderLoading={() => }
+ renderDay={(day, _value, DayComponentProps) => {
+ const isSelected =
+ !DayComponentProps.outsideCurrentMonth &&
+ highlightedDays.indexOf(day.getDate()) > 0;
+
+ return (
+
+
+
+ );
+ }}
+ />
+
+ );
+}
diff --git a/docs/src/pages/components/date-picker/StaticDatePickerDemo.js b/docs/src/pages/components/date-picker/StaticDatePickerDemo.js
new file mode 100644
index 00000000000000..8672cf992393fc
--- /dev/null
+++ b/docs/src/pages/components/date-picker/StaticDatePickerDemo.js
@@ -0,0 +1,23 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizaitonProvider from '@material-ui/lab/LocalizationProvider';
+import StaticDatePicker from '@material-ui/lab/StaticDatePicker';
+
+export default function StaticDatePickerDemo() {
+ const [value, setValue] = React.useState(new Date());
+
+ return (
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+
+ );
+}
diff --git a/docs/src/pages/components/date-picker/StaticDatePickerDemo.tsx b/docs/src/pages/components/date-picker/StaticDatePickerDemo.tsx
new file mode 100644
index 00000000000000..0afc3390aee734
--- /dev/null
+++ b/docs/src/pages/components/date-picker/StaticDatePickerDemo.tsx
@@ -0,0 +1,23 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizaitonProvider from '@material-ui/lab/LocalizationProvider';
+import StaticDatePicker from '@material-ui/lab/StaticDatePicker';
+
+export default function StaticDatePickerDemo() {
+ const [value, setValue] = React.useState(new Date());
+
+ return (
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+
+ );
+}
diff --git a/docs/src/pages/components/date-picker/StaticDatePickerLandscape.js b/docs/src/pages/components/date-picker/StaticDatePickerLandscape.js
new file mode 100644
index 00000000000000..16022e1cf500b1
--- /dev/null
+++ b/docs/src/pages/components/date-picker/StaticDatePickerLandscape.js
@@ -0,0 +1,25 @@
+import * as React from 'react';
+import isWeekend from 'date-fns/isWeekend';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizaitonProvider from '@material-ui/lab/LocalizationProvider';
+import StaticDatePicker from '@material-ui/lab/StaticDatePicker';
+
+export default function StaticDatePickerLandscape() {
+ const [value, setValue] = React.useState(new Date());
+
+ return (
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+
+ );
+}
diff --git a/docs/src/pages/components/date-picker/StaticDatePickerLandscape.tsx b/docs/src/pages/components/date-picker/StaticDatePickerLandscape.tsx
new file mode 100644
index 00000000000000..b7fd17a98a51c3
--- /dev/null
+++ b/docs/src/pages/components/date-picker/StaticDatePickerLandscape.tsx
@@ -0,0 +1,25 @@
+import * as React from 'react';
+import isWeekend from 'date-fns/isWeekend';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizaitonProvider from '@material-ui/lab/LocalizationProvider';
+import StaticDatePicker from '@material-ui/lab/StaticDatePicker';
+
+export default function StaticDatePickerLandscape() {
+ const [value, setValue] = React.useState(new Date());
+
+ return (
+
+
+ orientation="landscape"
+ openTo="date"
+ value={value}
+ shouldDisableDate={isWeekend}
+ onChange={(newValue) => {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+
+ );
+}
diff --git a/docs/src/pages/components/date-picker/ViewsDatePicker.js b/docs/src/pages/components/date-picker/ViewsDatePicker.js
new file mode 100644
index 00000000000000..0297d95edb10f0
--- /dev/null
+++ b/docs/src/pages/components/date-picker/ViewsDatePicker.js
@@ -0,0 +1,74 @@
+import React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizaitonProvider from '@material-ui/lab/LocalizationProvider';
+import DatePicker from '@material-ui/lab/DatePicker';
+
+export default function ViewsDatePicker() {
+ const [value, setValue] = React.useState(new Date());
+
+ return (
+
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => (
+
+ )}
+ />
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => (
+
+ )}
+ />
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => (
+
+ )}
+ />
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => (
+
+ )}
+ />
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => (
+
+ )}
+ />
+
+
+ );
+}
diff --git a/docs/src/pages/components/date-picker/ViewsDatePicker.tsx b/docs/src/pages/components/date-picker/ViewsDatePicker.tsx
new file mode 100644
index 00000000000000..bc53ca7dfdfbbd
--- /dev/null
+++ b/docs/src/pages/components/date-picker/ViewsDatePicker.tsx
@@ -0,0 +1,74 @@
+import React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizaitonProvider from '@material-ui/lab/LocalizationProvider';
+import DatePicker from '@material-ui/lab/DatePicker';
+
+export default function ViewsDatePicker() {
+ const [value, setValue] = React.useState(new Date());
+
+ return (
+
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => (
+
+ )}
+ />
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => (
+
+ )}
+ />
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => (
+
+ )}
+ />
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => (
+
+ )}
+ />
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => (
+
+ )}
+ />
+
+
+ );
+}
diff --git a/docs/src/pages/components/date-picker/date-picker.md b/docs/src/pages/components/date-picker/date-picker.md
new file mode 100644
index 00000000000000..58f1b2347a0355
--- /dev/null
+++ b/docs/src/pages/components/date-picker/date-picker.md
@@ -0,0 +1,105 @@
+---
+title: React Date Picker component
+components: DatePicker, PickersDay
+githubLabel: 'component: DatePicker'
+packageName: '@material-ui/lab'
+materialDesign: https://material.io/components/date-pickers
+---
+
+# Date Picker
+
+Date pickers let the user select a date.
+
+Date pickers let the user select a date. Date pickers are displayed with:
+
+- Dialogs on mobile
+- Text field dropdowns on desktop
+
+{{"component": "modules/components/ComponentLinkHeader.js"}}
+
+## Requirements
+
+This component relies on the date management library of your choice. It supports [date-fns](https://date-fns.org/), [luxon](https://moment.github.io/luxon/), [dayjs](https://github.com/iamkun/dayjs), [moment](https://momentjs.com/) and any other library via a public `dateAdapter` interface.
+
+Please install any of these libraries and set up the right date engine by wrapping your root (or the highest level you wish the pickers to be available) with `LocalizationProvider`:
+
+```jsx
+// or @material-ui/lab/dateAdapter/{dayjs,luxon,moment} or any valid date-io adapter
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+
+function App() {
+ return (
+
+ ...
+
+ );
+}
+```
+
+## Basic usage
+
+The date picker will be rendered as a modal dialog on mobile, and a textfield with a popover on desktop.
+
+{{"demo": "pages/components/date-picker/BasicDatePicker.js"}}
+
+## Responsiveness
+
+The date picker component is designed and optimized for the device it runs on.
+
+- The "Mobile" version works best for touch devices and small screens.
+- The "Desktop" version works best for mouse devices and large screens.
+
+By default, the `DatePicker` component uses a `@media (pointer: fine)` media query to determine which version to use.
+This can be customized with the `desktopModeMediaQuery` prop.
+
+{{"demo": "pages/components/date-picker/ResponsiveDatePickers.js"}}
+
+## Localization
+
+Use `LocalizationProvider` to change the date-engine locale that is used to render the date picker. Here is an example of changing the locale for the `date-fns` adapter:
+
+{{"demo": "pages/components/date-picker/LocalizedDatePicker.js"}}
+
+## Views playground
+
+It's possible to combine `year`, `month`, and `date` selection views. Views will appear in the order they're included in the `views` array.
+
+{{"demo": "pages/components/date-picker/ViewsDatePicker.js"}}
+
+## Static mode
+
+It's possible to render any picker without the modal/popover and text field. This can be helpful when building custom popover/modal containers.
+
+{{"demo": "pages/components/date-picker/StaticDatePickerDemo.js", "bg": true}}
+
+## Landscape orientation
+
+For ease of use the date picker will automatically change the layout between portrait and landscape by subscription to the `window.orientation` change. You can force a specific layout using the `orientation` prop.
+
+{{"demo": "pages/components/date-picker/StaticDatePickerLandscape.js", "bg": true}}
+
+## Sub-components
+
+Some lower level sub-components (`DayPicker`, `MonthPicker` and `YearPicker`) are also exported. These are rendered without a wrapper or outer logic (masked input, date values parsing and validation, etc.).
+
+{{"demo": "pages/components/date-picker/InternalPickers.js"}}
+
+## Custom input component
+
+You can customize rendering of the input with the `renderInput` prop. Make sure to spread `ref` and `inputProps` correctly to the custom input component.
+
+{{"demo": "pages/components/date-picker/CustomInput.js"}}
+
+## Customized day rendering
+
+The displayed days are customizable with the `renderDay` function prop.
+You can take advantage of the internal [PickersDay](/api/pickers-day) component.
+
+{{"demo": "pages/components/date-picker/CustomDay.js"}}
+
+## Dynamic data
+
+Sometimes it may be necessary to display additional info right in the calendar. Here's an example of prefetching and displaying server-side data using the `onMonthChange`, `loading`, and `renderDay` props.
+
+{{"demo": "pages/components/date-picker/ServerRequestDatePicker.js"}}
diff --git a/docs/src/pages/components/date-range-picker/BasicDateRangePicker.js b/docs/src/pages/components/date-range-picker/BasicDateRangePicker.js
new file mode 100644
index 00000000000000..820556c9d393e7
--- /dev/null
+++ b/docs/src/pages/components/date-range-picker/BasicDateRangePicker.js
@@ -0,0 +1,30 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateRangePicker from '@material-ui/lab/DateRangePicker';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DateRangeDelimiter from '@material-ui/lab/DateRangeDelimiter';
+
+export default function BasicDateRangePicker() {
+ const [value, setValue] = React.useState([null, null]);
+
+ return (
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(startProps, endProps) => (
+
+
+ to
+
+
+ )}
+ />
+
+ );
+}
diff --git a/docs/src/pages/components/date-range-picker/BasicDateRangePicker.tsx b/docs/src/pages/components/date-range-picker/BasicDateRangePicker.tsx
new file mode 100644
index 00000000000000..a41da0dbdf94ab
--- /dev/null
+++ b/docs/src/pages/components/date-range-picker/BasicDateRangePicker.tsx
@@ -0,0 +1,30 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateRangePicker, { DateRange } from '@material-ui/lab/DateRangePicker';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DateRangeDelimiter from '@material-ui/lab/DateRangeDelimiter';
+
+export default function BasicDateRangePicker() {
+ const [value, setValue] = React.useState>([null, null]);
+
+ return (
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(startProps, endProps) => (
+
+
+ to
+
+
+ )}
+ />
+
+ );
+}
diff --git a/docs/src/pages/components/date-range-picker/CalendarsDateRangePicker.js b/docs/src/pages/components/date-range-picker/CalendarsDateRangePicker.js
new file mode 100644
index 00000000000000..598cf7a15888b3
--- /dev/null
+++ b/docs/src/pages/components/date-range-picker/CalendarsDateRangePicker.js
@@ -0,0 +1,64 @@
+import * as React from 'react';
+import Grid from '@material-ui/core/Grid';
+import Typography from '@material-ui/core/Typography';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DateRangePicker from '@material-ui/lab/DateRangePicker';
+import DateRangeDelimiter from '@material-ui/lab/DateRangeDelimiter';
+
+export default function CalendarsDateRangePicker() {
+ const [value, setValue] = React.useState([null, null]);
+
+ return (
+
+
+ 1 calendar
+ {
+ setValue(newValue);
+ }}
+ renderInput={(startProps, endProps) => (
+
+
+ to
+
+
+ )}
+ />
+ 2 calendars
+ {
+ setValue(newValue);
+ }}
+ renderInput={(startProps, endProps) => (
+
+
+ to
+
+
+ )}
+ />
+ 3 calendars
+ {
+ setValue(newValue);
+ }}
+ renderInput={(startProps, endProps) => (
+
+
+ to
+
+
+ )}
+ />
+
+
+ );
+}
diff --git a/docs/src/pages/components/date-range-picker/CalendarsDateRangePicker.tsx b/docs/src/pages/components/date-range-picker/CalendarsDateRangePicker.tsx
new file mode 100644
index 00000000000000..ccbb195d05b594
--- /dev/null
+++ b/docs/src/pages/components/date-range-picker/CalendarsDateRangePicker.tsx
@@ -0,0 +1,64 @@
+import * as React from 'react';
+import Grid from '@material-ui/core/Grid';
+import Typography from '@material-ui/core/Typography';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DateRangePicker, { DateRange } from '@material-ui/lab/DateRangePicker';
+import DateRangeDelimiter from '@material-ui/lab/DateRangeDelimiter';
+
+export default function CalendarsDateRangePicker() {
+ const [value, setValue] = React.useState>([null, null]);
+
+ return (
+
+
+ 1 calendar
+ {
+ setValue(newValue);
+ }}
+ renderInput={(startProps, endProps) => (
+
+
+ to
+
+
+ )}
+ />
+ 2 calendars
+ {
+ setValue(newValue);
+ }}
+ renderInput={(startProps, endProps) => (
+
+
+ to
+
+
+ )}
+ />
+ 3 calendars
+ {
+ setValue(newValue);
+ }}
+ renderInput={(startProps, endProps) => (
+
+
+ to
+
+
+ )}
+ />
+
+
+ );
+}
diff --git a/docs/src/pages/components/date-range-picker/CustomDateRangeInputs.js b/docs/src/pages/components/date-range-picker/CustomDateRangeInputs.js
new file mode 100644
index 00000000000000..96973e426ecba0
--- /dev/null
+++ b/docs/src/pages/components/date-range-picker/CustomDateRangeInputs.js
@@ -0,0 +1,24 @@
+import * as React from 'react';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DateRangePicker from '@material-ui/lab/DateRangePicker';
+
+export default function CustomDateRangeInputs() {
+ const [selectedDate, handleDateChange] = React.useState([null, null]);
+
+ return (
+
+ handleDateChange(date)}
+ renderInput={(startProps, endProps) => (
+
+
+
+
+ )}
+ />
+
+ );
+}
diff --git a/docs/src/pages/components/date-range-picker/CustomDateRangeInputs.tsx b/docs/src/pages/components/date-range-picker/CustomDateRangeInputs.tsx
new file mode 100644
index 00000000000000..8c595e73e35b8c
--- /dev/null
+++ b/docs/src/pages/components/date-range-picker/CustomDateRangeInputs.tsx
@@ -0,0 +1,33 @@
+import * as React from 'react';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DateRangePicker, { DateRange } from '@material-ui/lab/DateRangePicker';
+
+export default function CustomDateRangeInputs() {
+ const [selectedDate, handleDateChange] = React.useState>([
+ null,
+ null,
+ ]);
+
+ return (
+
+ handleDateChange(date)}
+ renderInput={(startProps, endProps) => (
+
+ }
+ {...startProps.inputProps}
+ />
+ }
+ {...endProps.inputProps}
+ />
+
+ )}
+ />
+
+ );
+}
diff --git a/docs/src/pages/components/date-range-picker/MinMaxDateRangePicker.js b/docs/src/pages/components/date-range-picker/MinMaxDateRangePicker.js
new file mode 100644
index 00000000000000..75bb9caf8879db
--- /dev/null
+++ b/docs/src/pages/components/date-range-picker/MinMaxDateRangePicker.js
@@ -0,0 +1,35 @@
+import * as React from 'react';
+import addWeeks from 'date-fns/addWeeks';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DateRangePicker from '@material-ui/lab/DateRangePicker';
+import DateRangeDelimiter from '@material-ui/lab/DateRangeDelimiter';
+
+function getWeeksAfter(date, amount) {
+ return date ? addWeeks(date, amount) : undefined;
+}
+
+export default function MinMaxDateRangePicker() {
+ const [value, setValue] = React.useState([null, null]);
+
+ return (
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(startProps, endProps) => (
+
+
+ to
+
+
+ )}
+ />
+
+ );
+}
diff --git a/docs/src/pages/components/date-range-picker/MinMaxDateRangePicker.tsx b/docs/src/pages/components/date-range-picker/MinMaxDateRangePicker.tsx
new file mode 100644
index 00000000000000..a3389354d33a15
--- /dev/null
+++ b/docs/src/pages/components/date-range-picker/MinMaxDateRangePicker.tsx
@@ -0,0 +1,35 @@
+import * as React from 'react';
+import addWeeks from 'date-fns/addWeeks';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DateRangePicker, { DateRange } from '@material-ui/lab/DateRangePicker';
+import DateRangeDelimiter from '@material-ui/lab/DateRangeDelimiter';
+
+function getWeeksAfter(date: Date | null, amount: number) {
+ return date ? addWeeks(date, amount) : undefined;
+}
+
+export default function MinMaxDateRangePicker() {
+ const [value, setValue] = React.useState>([null, null]);
+
+ return (
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(startProps, endProps) => (
+
+
+ to
+
+
+ )}
+ />
+
+ );
+}
diff --git a/docs/src/pages/components/date-range-picker/ResponsiveDateRangePicker.js b/docs/src/pages/components/date-range-picker/ResponsiveDateRangePicker.js
new file mode 100644
index 00000000000000..7b19699ad4dc28
--- /dev/null
+++ b/docs/src/pages/components/date-range-picker/ResponsiveDateRangePicker.js
@@ -0,0 +1,44 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DateRangeDelimiter from '@material-ui/lab/DateRangeDelimiter';
+import MobileDateRangePicker from '@material-ui/lab/MobileDateRangePicker';
+import DesktopDateRangePicker from '@material-ui/lab/DesktopDateRangePicker';
+
+export default function ResponsiveDateRangePicker() {
+ const [value, setValue] = React.useState([null, null]);
+
+ return (
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(startProps, endProps) => (
+
+
+ to
+
+
+ )}
+ />
+ {
+ setValue(newValue);
+ }}
+ renderInput={(startProps, endProps) => (
+
+
+ to
+
+
+ )}
+ />
+
+ );
+}
diff --git a/packages/pickers/docs/pages/demo/daterangepicker/ResponsiveDateRangePicker.example.tsx b/docs/src/pages/components/date-range-picker/ResponsiveDateRangePicker.tsx
similarity index 60%
rename from packages/pickers/docs/pages/demo/daterangepicker/ResponsiveDateRangePicker.example.tsx
rename to docs/src/pages/components/date-range-picker/ResponsiveDateRangePicker.tsx
index 67cf7f19586791..872aa6b735bce4 100644
--- a/packages/pickers/docs/pages/demo/daterangepicker/ResponsiveDateRangePicker.example.tsx
+++ b/docs/src/pages/components/date-range-picker/ResponsiveDateRangePicker.tsx
@@ -1,21 +1,24 @@
import * as React from 'react';
import TextField from '@material-ui/core/TextField';
-import {
- MobileDateRangePicker,
- DateRangeDelimiter,
- DesktopDateRangePicker,
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DateRangeDelimiter from '@material-ui/lab/DateRangeDelimiter';
+import MobileDateRangePicker, {
DateRange,
-} from '@material-ui/pickers';
+} from '@material-ui/lab/MobileDateRangePicker';
+import DesktopDateRangePicker from '@material-ui/lab/DesktopDateRangePicker';
export default function ResponsiveDateRangePicker() {
const [value, setValue] = React.useState>([null, null]);
return (
-
+
setValue(newValue)}
+ onChange={(newValue) => {
+ setValue(newValue);
+ }}
renderInput={(startProps, endProps) => (
@@ -27,7 +30,9 @@ export default function ResponsiveDateRangePicker() {
setValue(newValue)}
+ onChange={(newValue) => {
+ setValue(newValue);
+ }}
renderInput={(startProps, endProps) => (
@@ -36,6 +41,6 @@ export default function ResponsiveDateRangePicker() {
)}
/>
-
+
);
}
diff --git a/docs/src/pages/components/date-range-picker/StaticDateRangePicker.js b/docs/src/pages/components/date-range-picker/StaticDateRangePicker.js
new file mode 100644
index 00000000000000..2d6e76e6f78ecc
--- /dev/null
+++ b/docs/src/pages/components/date-range-picker/StaticDateRangePicker.js
@@ -0,0 +1,29 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import StaticDateRangePicker from '@material-ui/lab/StaticDateRangePicker';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DateRangeDelimiter from '@material-ui/lab/DateRangeDelimiter';
+
+export default function StaticDateRangePickerExample() {
+ const [value, setValue] = React.useState([null, null]);
+
+ return (
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(startProps, endProps) => (
+
+
+ to
+
+
+ )}
+ />
+
+ );
+}
diff --git a/docs/src/pages/components/date-range-picker/StaticDateRangePicker.tsx b/docs/src/pages/components/date-range-picker/StaticDateRangePicker.tsx
new file mode 100644
index 00000000000000..c8173872d206a4
--- /dev/null
+++ b/docs/src/pages/components/date-range-picker/StaticDateRangePicker.tsx
@@ -0,0 +1,31 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import StaticDateRangePicker, {
+ DateRange,
+} from '@material-ui/lab/StaticDateRangePicker';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DateRangeDelimiter from '@material-ui/lab/DateRangeDelimiter';
+
+export default function StaticDateRangePickerExample() {
+ const [value, setValue] = React.useState>([null, null]);
+
+ return (
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(startProps, endProps) => (
+
+
+ to
+
+
+ )}
+ />
+
+ );
+}
diff --git a/docs/src/pages/components/date-range-picker/date-range-picker.md b/docs/src/pages/components/date-range-picker/date-range-picker.md
new file mode 100644
index 00000000000000..340da42b935179
--- /dev/null
+++ b/docs/src/pages/components/date-range-picker/date-range-picker.md
@@ -0,0 +1,83 @@
+---
+title: React Date Range Picker component
+components: DateRangePicker
+githubLabel: 'component: DateRangePicker'
+packageName: '@material-ui/lab'
+materialDesign: https://material.io/components/date-pickers
+---
+
+# Date Range Picker [⚡️ ](https://material-ui.com/store/items/material-ui-x/)
+
+Date pickers let the user select a range of dates.
+
+> ⚠️⚠️ The date range picker is unstable, and **not suitable** for use in production. ⚠️⚠️
+>
+> The date range picker will be made available in the coming months for production use as part of a paid extension (commercial license) to the community edition (MIT license) of Material-UI.
+> This paid extension will include advanced components (rich data grid, date range picker, tree view drag & drop, etc.). [Early access](https://material-ui.com/store/items/material-ui-x/) starts at an affordable price.
+
+The date range pickers let the user select a range of dates.
+
+{{"component": "modules/components/ComponentLinkHeader.js"}}
+
+## Requirements
+
+This component relies on the date management library of your choice. It supports [date-fns](https://date-fns.org/), [luxon](https://moment.github.io/luxon/), [dayjs](https://github.com/iamkun/dayjs), [moment](https://momentjs.com/) and any other library via a public `dateAdapter` interface.
+
+Please install any of these libraries and set up the right date engine by wrapping your root (or the highest level you wish the pickers to be available) with `LocalizationProvider`:
+
+```jsx
+// or @material-ui/lab/dateAdapter/{dayjs,luxon,moment} or any valid date-io adapter
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+
+function App() {
+ return (
+
+ ...
+
+ );
+}
+```
+
+## Basic usage
+
+Note that you can pass almost any prop from [DatePicker]('/api/date-picker/').
+
+{{"demo": "pages/components/date-range-picker/BasicDateRangePicker.js"}}
+
+## Responsiveness
+
+The date range picker component is designed to be optimized for the device it runs on.
+
+- The "Mobile" version works best for touch devices and small screens.
+- The "Desktop" version works best for mouse devices and large screens.
+
+By default, the `DateRangePicker` component uses a `@media (pointer: fine)` media query to determine which version to use.
+This can be customized with the `desktopModeMediaQuery` prop.
+
+{{"demo": "pages/components/date-range-picker/ResponsiveDateRangePicker.js"}}
+
+## Different number of months
+
+Note that the `calendars` prop only works in desktop mode.
+
+{{"demo": "pages/components/date-range-picker/CalendarsDateRangePicker.js"}}
+
+## Disabling dates
+
+Disabling dates behaves the same as the simple `DatePicker`.
+
+{{"demo": "pages/components/date-range-picker/MinMaxDateRangePicker.js"}}
+
+## Custom input component
+
+You can customize the rendered input with the `renderInput` prop. For `DateRangePicker` it takes **2** parameters – for start and end input respectively.
+If you need to render custom inputs make sure to spread `ref` and `inputProps` correctly to the input components.
+
+{{"demo": "pages/components/date-range-picker/CustomDateRangeInputs.js"}}
+
+## Static mode
+
+It is possible to render any picker without a modal or popper. For this use `StaticDateRangePicker`.
+
+{{"demo": "pages/components/date-range-picker/StaticDateRangePicker.js"}}
diff --git a/docs/src/pages/components/date-time-picker/BasicDateTimePicker.js b/docs/src/pages/components/date-time-picker/BasicDateTimePicker.js
new file mode 100644
index 00000000000000..b9c629297a01c8
--- /dev/null
+++ b/docs/src/pages/components/date-time-picker/BasicDateTimePicker.js
@@ -0,0 +1,20 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DateTimePicker from '@material-ui/lab/DateTimePicker';
+
+export default function BasicDateTimePicker() {
+ const [selectedDate, handleDateChange] = React.useState(new Date());
+
+ return (
+
+ }
+ label="DateTimePicker"
+ value={selectedDate}
+ onChange={handleDateChange}
+ />
+
+ );
+}
diff --git a/packages/pickers/docs/pages/demo/datetime-picker/BasicDateTimePicker.example.tsx b/docs/src/pages/components/date-time-picker/BasicDateTimePicker.tsx
similarity index 57%
rename from packages/pickers/docs/pages/demo/datetime-picker/BasicDateTimePicker.example.tsx
rename to docs/src/pages/components/date-time-picker/BasicDateTimePicker.tsx
index c09161509794da..870355e74b7576 100644
--- a/packages/pickers/docs/pages/demo/datetime-picker/BasicDateTimePicker.example.tsx
+++ b/docs/src/pages/components/date-time-picker/BasicDateTimePicker.tsx
@@ -1,18 +1,22 @@
import * as React from 'react';
import TextField from '@material-ui/core/TextField';
-import { DateTimePicker } from '@material-ui/pickers';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DateTimePicker from '@material-ui/lab/DateTimePicker';
export default function BasicDateTimePicker() {
- const [selectedDate, handleDateChange] = React.useState(new Date());
+ const [selectedDate, handleDateChange] = React.useState(
+ new Date(),
+ );
return (
-
+
}
label="DateTimePicker"
value={selectedDate}
onChange={handleDateChange}
/>
-
+
);
}
diff --git a/docs/src/pages/components/date-time-picker/CustomDateTimePicker.js b/docs/src/pages/components/date-time-picker/CustomDateTimePicker.js
new file mode 100644
index 00000000000000..117b4f9acb394f
--- /dev/null
+++ b/docs/src/pages/components/date-time-picker/CustomDateTimePicker.js
@@ -0,0 +1,74 @@
+import * as React from 'react';
+import AlarmIcon from '@material-ui/icons/Alarm';
+import SnoozeIcon from '@material-ui/icons/Snooze';
+import TextField from '@material-ui/core/TextField';
+import ClockIcon from '@material-ui/icons/AccessTime';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DateTimePicker from '@material-ui/lab/DateTimePicker';
+import MobileDateTimePicker from '@material-ui/lab/MobileDateTimePicker';
+
+export default function CustomDateTimePicker() {
+ const [clearedDate, setClearedDate] = React.useState(null);
+ const [value, setValue] = React.useState(new Date('2019-01-01T18:54'));
+
+ return (
+
+
+
{
+ setValue(newValue);
+ }}
+ minDate={new Date('2018-01-01')}
+ leftArrowIcon={ }
+ rightArrowIcon={ }
+ leftArrowButtonText="Open previous month"
+ rightArrowButtonText="Open next month"
+ openPickerIcon={ }
+ minTime={new Date(0, 0, 0, 9)}
+ maxTime={new Date(0, 0, 0, 20)}
+ renderInput={(params) => (
+
+ )}
+ />
+ {
+ setValue(newValue);
+ }}
+ label="With error handler"
+ onError={console.log}
+ minDate={new Date('2018-01-01T00:00')}
+ inputFormat="yyyy/MM/dd hh:mm a"
+ mask="___/__/__ __:__ _M"
+ renderInput={(params) => (
+
+ )}
+ />
+ setClearedDate(newValue)}
+ renderInput={(params) => (
+
+ )}
+ />
+
+
+ );
+}
diff --git a/docs/src/pages/components/date-time-picker/CustomDateTimePicker.tsx b/docs/src/pages/components/date-time-picker/CustomDateTimePicker.tsx
new file mode 100644
index 00000000000000..1e62a25889135d
--- /dev/null
+++ b/docs/src/pages/components/date-time-picker/CustomDateTimePicker.tsx
@@ -0,0 +1,76 @@
+import * as React from 'react';
+import AlarmIcon from '@material-ui/icons/Alarm';
+import SnoozeIcon from '@material-ui/icons/Snooze';
+import TextField from '@material-ui/core/TextField';
+import ClockIcon from '@material-ui/icons/AccessTime';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DateTimePicker from '@material-ui/lab/DateTimePicker';
+import MobileDateTimePicker from '@material-ui/lab/MobileDateTimePicker';
+
+export default function CustomDateTimePicker() {
+ const [clearedDate, setClearedDate] = React.useState(null);
+ const [value, setValue] = React.useState(
+ new Date('2019-01-01T18:54'),
+ );
+
+ return (
+
+
+
{
+ setValue(newValue);
+ }}
+ minDate={new Date('2018-01-01')}
+ leftArrowIcon={ }
+ rightArrowIcon={ }
+ leftArrowButtonText="Open previous month"
+ rightArrowButtonText="Open next month"
+ openPickerIcon={ }
+ minTime={new Date(0, 0, 0, 9)}
+ maxTime={new Date(0, 0, 0, 20)}
+ renderInput={(params) => (
+
+ )}
+ />
+ {
+ setValue(newValue);
+ }}
+ label="With error handler"
+ onError={console.log}
+ minDate={new Date('2018-01-01T00:00')}
+ inputFormat="yyyy/MM/dd hh:mm a"
+ mask="___/__/__ __:__ _M"
+ renderInput={(params) => (
+
+ )}
+ />
+ setClearedDate(newValue)}
+ renderInput={(params) => (
+
+ )}
+ />
+
+
+ );
+}
diff --git a/docs/src/pages/components/date-time-picker/DateTimeValidation.js b/docs/src/pages/components/date-time-picker/DateTimeValidation.js
new file mode 100644
index 00000000000000..0f8fd008adfbf9
--- /dev/null
+++ b/docs/src/pages/components/date-time-picker/DateTimeValidation.js
@@ -0,0 +1,36 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DateTimePicker from '@material-ui/lab/DateTimePicker';
+
+export default function DateTimeValidation() {
+ const [value, setValue] = React.useState(new Date());
+
+ return (
+
+
+ }
+ label="Ignore date and time"
+ value={value}
+ onChange={(newValue) => {
+ setValue(newValue);
+ }}
+ minDateTime={new Date()}
+ />
+ }
+ label="Ignore time in each day"
+ value={value}
+ onChange={(newValue) => {
+ setValue(newValue);
+ }}
+ minDate={new Date('2020-02-14')}
+ minTime={new Date(0, 0, 0, 8)}
+ maxTime={new Date(0, 0, 0, 18, 45)}
+ />
+
+
+ );
+}
diff --git a/docs/src/pages/components/date-time-picker/DateTimeValidation.tsx b/docs/src/pages/components/date-time-picker/DateTimeValidation.tsx
new file mode 100644
index 00000000000000..18936a6cd6d60c
--- /dev/null
+++ b/docs/src/pages/components/date-time-picker/DateTimeValidation.tsx
@@ -0,0 +1,36 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DateTimePicker from '@material-ui/lab/DateTimePicker';
+
+export default function DateTimeValidation() {
+ const [value, setValue] = React.useState(new Date());
+
+ return (
+
+
+ }
+ label="Ignore date and time"
+ value={value}
+ onChange={(newValue) => {
+ setValue(newValue);
+ }}
+ minDateTime={new Date()}
+ />
+ }
+ label="Ignore time in each day"
+ value={value}
+ onChange={(newValue) => {
+ setValue(newValue);
+ }}
+ minDate={new Date('2020-02-14')}
+ minTime={new Date(0, 0, 0, 8)}
+ maxTime={new Date(0, 0, 0, 18, 45)}
+ />
+
+
+ );
+}
diff --git a/docs/src/pages/components/date-time-picker/ResponsiveDateTimePickers.js b/docs/src/pages/components/date-time-picker/ResponsiveDateTimePickers.js
new file mode 100644
index 00000000000000..1e0c9d1f511161
--- /dev/null
+++ b/docs/src/pages/components/date-time-picker/ResponsiveDateTimePickers.js
@@ -0,0 +1,41 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DateTimePicker from '@material-ui/lab/DateTimePicker';
+import MobileDateTimePicker from '@material-ui/lab/MobileDateTimePicker';
+import DesktopDateTimePicker from '@material-ui/lab/DesktopDateTimePicker';
+
+export default function ResponsiveDateTimePickers() {
+ const [value, setValue] = React.useState(
+ new Date('2018-01-01T00:00:00.000Z'),
+ );
+
+ return (
+
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+ }
+ value={value}
+ onChange={(newValue) => {
+ setValue(newValue);
+ }}
+ />
+
+
+ );
+}
diff --git a/docs/src/pages/components/date-time-picker/ResponsiveDateTimePickers.tsx b/docs/src/pages/components/date-time-picker/ResponsiveDateTimePickers.tsx
new file mode 100644
index 00000000000000..b730f644cba691
--- /dev/null
+++ b/docs/src/pages/components/date-time-picker/ResponsiveDateTimePickers.tsx
@@ -0,0 +1,41 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DateTimePicker from '@material-ui/lab/DateTimePicker';
+import MobileDateTimePicker from '@material-ui/lab/MobileDateTimePicker';
+import DesktopDateTimePicker from '@material-ui/lab/DesktopDateTimePicker';
+
+export default function ResponsiveDateTimePickers() {
+ const [value, setValue] = React.useState(
+ new Date('2018-01-01T00:00:00.000Z'),
+ );
+
+ return (
+
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+ }
+ value={value}
+ onChange={(newValue) => {
+ setValue(newValue);
+ }}
+ />
+
+
+ );
+}
diff --git a/docs/src/pages/components/date-time-picker/date-time-picker.md b/docs/src/pages/components/date-time-picker/date-time-picker.md
new file mode 100644
index 00000000000000..d192666a7c59d3
--- /dev/null
+++ b/docs/src/pages/components/date-time-picker/date-time-picker.md
@@ -0,0 +1,71 @@
+---
+title: React Date Time Picker component
+components: DateTimePicker
+githubLabel: 'component: DateTimePicker'
+packageName: '@material-ui/lab'
+materialDesign: https://material.io/components/date-pickers
+---
+
+# Date Time Picker
+
+Combined date & time picker.
+
+This component combines the date & time pickers. It allows the user to select both date and time with the same control.
+
+Note that this component is the [DatePicker](/components/date-picker/) and [TimePicker](/components/time-picker/)
+component combined, so any of these components' props can be passed to the DateTimePicker.
+
+{{"component": "modules/components/ComponentLinkHeader.js"}}
+
+## Requirements
+
+This component relies on the date management library of your choice. It supports [date-fns](https://date-fns.org/), [luxon](https://moment.github.io/luxon/), [dayjs](https://github.com/iamkun/dayjs), [moment](https://momentjs.com/) and any other library via a public `dateAdapter` interface.
+
+Please install any of these libraries and set up the right date engine by wrapping your root (or the highest level you wish the pickers to be available) with `LocalizationProvider`:
+
+```jsx
+// or @material-ui/lab/dateAdapter/{dayjs,luxon,moment} or any valid date-io adapter
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+
+function App() {
+ return (
+
+ ...
+
+ );
+}
+```
+
+## Basic usage
+
+Allows choosing date then time. There are 4 steps available (year, date, hour and minute), so tabs are required to visually distinguish date/time steps.
+
+{{"demo": "pages/components/date-time-picker/BasicDateTimePicker.js"}}
+
+## Responsiveness
+
+The `DateTimePicker` component is designed and optimized for the device it runs on.
+
+- The "Mobile" version works best for touch devices and small screens.
+- The "Desktop" version works best for mouse devices and large screens.
+
+By default, the `DateTimePicker` component uses a `@media (pointer: fine)` media query to determine which version to use.
+This can be customized with the `desktopModeMediaQuery` prop.
+
+{{"demo": "pages/components/date-time-picker/ResponsiveDateTimePickers.js"}}
+
+## Date and time validation
+
+It is possible to restrict date and time selection in two ways:
+
+- by using `minDateTime`/`maxDateTime` its possible to restrict time selection to before or after a particular moment in time
+- using `minTime`/`maxTime`, you can disable selecting times before or after a certain time each day respectively
+
+{{"demo": "pages/components/date-time-picker/DateTimeValidation.js"}}
+
+## Customization
+
+Here are some examples of heavily customized date & time pickers:
+
+{{"demo": "pages/components/date-time-picker/CustomDateTimePicker.js"}}
diff --git a/docs/src/pages/components/pickers/MaterialUIPickers.js b/docs/src/pages/components/pickers/MaterialUIPickers.js
index 9a5c4a9898f5cb..065bad26371316 100644
--- a/docs/src/pages/components/pickers/MaterialUIPickers.js
+++ b/docs/src/pages/components/pickers/MaterialUIPickers.js
@@ -1,16 +1,13 @@
import * as React from 'react';
import Grid from '@material-ui/core/Grid';
import TextField from '@material-ui/core/TextField';
-import DateFnsAdapter from '@material-ui/pickers/adapter/date-fns';
-import {
- LocalizationProvider as MuiPickersLocalizationProvider,
- TimePicker,
- DesktopDatePicker,
- MobileDatePicker,
-} from '@material-ui/pickers';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import TimePicker from '@material-ui/lab/TimePicker';
+import DesktopDatePicker from '@material-ui/lab/DesktopDatePicker';
+import MobileDatePicker from '@material-ui/lab/MobileDatePicker';
export default function MaterialUIPickers() {
- // The first commit of Material-UI
const [selectedDate, setSelectedDate] = React.useState(
new Date('2014-08-18T21:11:54'),
);
@@ -20,15 +17,15 @@ export default function MaterialUIPickers() {
};
return (
-
+
(
-
+ renderInput={(params) => (
+
)}
OpenPickerButtonProps={{
'aria-label': 'change date',
@@ -39,8 +36,8 @@ export default function MaterialUIPickers() {
inputFormat="MM/dd/yyyy"
value={selectedDate}
onChange={handleDateChange}
- renderInput={(props) => (
-
+ renderInput={(params) => (
+
)}
OpenPickerButtonProps={{
'aria-label': 'change date',
@@ -50,12 +47,12 @@ export default function MaterialUIPickers() {
label="Time picker"
value={selectedDate}
onChange={handleDateChange}
- renderInput={(props) => }
+ renderInput={(params) => }
OpenPickerButtonProps={{
'aria-label': 'change time',
}}
/>
-
+
);
}
diff --git a/docs/src/pages/components/pickers/MaterialUIPickers.tsx b/docs/src/pages/components/pickers/MaterialUIPickers.tsx
index 7dbf199d57a204..512877d487b144 100644
--- a/docs/src/pages/components/pickers/MaterialUIPickers.tsx
+++ b/docs/src/pages/components/pickers/MaterialUIPickers.tsx
@@ -1,16 +1,13 @@
import * as React from 'react';
import Grid from '@material-ui/core/Grid';
import TextField from '@material-ui/core/TextField';
-import DateFnsAdapter from '@material-ui/pickers/adapter/date-fns';
-import {
- LocalizationProvider as MuiPickersLocalizationProvider,
- TimePicker,
- DesktopDatePicker,
- MobileDatePicker,
-} from '@material-ui/pickers';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import TimePicker from '@material-ui/lab/TimePicker';
+import DesktopDatePicker from '@material-ui/lab/DesktopDatePicker';
+import MobileDatePicker from '@material-ui/lab/MobileDatePicker';
export default function MaterialUIPickers() {
- // The first commit of Material-UI
const [selectedDate, setSelectedDate] = React.useState(
new Date('2014-08-18T21:11:54'),
);
@@ -20,15 +17,15 @@ export default function MaterialUIPickers() {
};
return (
-
+
(
-
+ renderInput={(params) => (
+
)}
OpenPickerButtonProps={{
'aria-label': 'change date',
@@ -39,8 +36,8 @@ export default function MaterialUIPickers() {
inputFormat="MM/dd/yyyy"
value={selectedDate}
onChange={handleDateChange}
- renderInput={(props) => (
-
+ renderInput={(params) => (
+
)}
OpenPickerButtonProps={{
'aria-label': 'change date',
@@ -50,12 +47,12 @@ export default function MaterialUIPickers() {
label="Time picker"
value={selectedDate}
onChange={handleDateChange}
- renderInput={(props) => }
+ renderInput={(params) => }
OpenPickerButtonProps={{
'aria-label': 'change time',
}}
/>
-
+
);
}
diff --git a/docs/src/pages/components/pickers/pickers.md b/docs/src/pages/components/pickers/pickers.md
index cfec750aee9fd4..af4aa0c17a8e5b 100644
--- a/docs/src/pages/components/pickers/pickers.md
+++ b/docs/src/pages/components/pickers/pickers.md
@@ -16,19 +16,13 @@ packageName: '@material-ui/lab'
{{"component": "modules/components/ComponentLinkHeader.js"}}
-## @material-ui/pickers
-
-![stars](https://img.shields.io/github/stars/mui-org/material-ui-pickers.svg?style=social&label=Stars)
-![npm downloads](https://img.shields.io/npm/dm/@material-ui/pickers.svg)
-
-[@material-ui/pickers](https://material-ui-pickers.dev/) provides date picker and time picker controls.
+## React components
{{"demo": "pages/components/pickers/MaterialUIPickers.js"}}
## Native pickers
⚠️ Native input controls support by browsers [isn't perfect](https://caniuse.com/#feat=input-datetime).
-Have a look at [@material-ui/pickers](https://material-ui-pickers.dev/) for a richer solution.
### Datepickers
diff --git a/docs/src/pages/components/progress/DelayingAppearance.js b/docs/src/pages/components/progress/DelayingAppearance.js
index ff03dd29a768ea..826a99045294dc 100644
--- a/docs/src/pages/components/progress/DelayingAppearance.js
+++ b/docs/src/pages/components/progress/DelayingAppearance.js
@@ -37,7 +37,9 @@ export default function DelayingAppearance() {
};
const handleClickQuery = () => {
- clearTimeout(timerRef.current);
+ if (timerRef.current) {
+ clearTimeout(timerRef.current);
+ }
if (query !== 'idle') {
setQuery('idle');
diff --git a/docs/src/pages/components/progress/DelayingAppearance.tsx b/docs/src/pages/components/progress/DelayingAppearance.tsx
index 03e15e1899df59..5ba7af3f0a5c9e 100644
--- a/docs/src/pages/components/progress/DelayingAppearance.tsx
+++ b/docs/src/pages/components/progress/DelayingAppearance.tsx
@@ -39,7 +39,9 @@ export default function DelayingAppearance() {
};
const handleClickQuery = () => {
- clearTimeout(timerRef.current);
+ if (timerRef.current) {
+ clearTimeout(timerRef.current);
+ }
if (query !== 'idle') {
setQuery('idle');
diff --git a/docs/src/pages/components/time-picker/BasicTimePicker.js b/docs/src/pages/components/time-picker/BasicTimePicker.js
new file mode 100644
index 00000000000000..e8706e1df15c00
--- /dev/null
+++ b/docs/src/pages/components/time-picker/BasicTimePicker.js
@@ -0,0 +1,33 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import TimePicker from '@material-ui/lab/TimePicker';
+
+export default function BasicTimePicker() {
+ const [value, setValue] = React.useState(new Date());
+
+ return (
+
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+
+
+ );
+}
diff --git a/docs/src/pages/components/time-picker/BasicTimePicker.tsx b/docs/src/pages/components/time-picker/BasicTimePicker.tsx
new file mode 100644
index 00000000000000..9298df571b262c
--- /dev/null
+++ b/docs/src/pages/components/time-picker/BasicTimePicker.tsx
@@ -0,0 +1,33 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import TimePicker from '@material-ui/lab/TimePicker';
+
+export default function BasicTimePicker() {
+ const [value, setValue] = React.useState(new Date());
+
+ return (
+
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+
+
+ );
+}
diff --git a/docs/src/pages/components/time-picker/LocalizedTimePicker.js b/docs/src/pages/components/time-picker/LocalizedTimePicker.js
new file mode 100644
index 00000000000000..cf38f994d9b9e2
--- /dev/null
+++ b/docs/src/pages/components/time-picker/LocalizedTimePicker.js
@@ -0,0 +1,56 @@
+import * as React from 'react';
+import frLocale from 'date-fns/locale/fr';
+import ruLocale from 'date-fns/locale/ru';
+import arSaLocale from 'date-fns/locale/ar-SA';
+import enLocale from 'date-fns/locale/en-US';
+import ToggleButton from '@material-ui/core/ToggleButton';
+import ToggleButtonGroup from '@material-ui/core/ToggleButtonGroup';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+
+import TimePicker from '@material-ui/lab/TimePicker';
+
+const localeMap = {
+ en: enLocale,
+ fr: frLocale,
+ ru: ruLocale,
+ ar: arSaLocale,
+};
+
+export default function LocalizedTimePicker() {
+ const [locale, setLocale] = React.useState('ru');
+ const [selectedDate, handleDateChange] = React.useState(new Date());
+
+ const selectLocale = (newLocale) => {
+ setLocale(newLocale);
+ };
+
+ return (
+
+
+
+ handleDateChange(date)}
+ renderInput={(params) => }
+ />
+
+ {Object.keys(localeMap).map((localeItem) => (
+ selectLocale(localeItem)}
+ >
+ {localeItem}
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/docs/src/pages/components/time-picker/LocalizedTimePicker.tsx b/docs/src/pages/components/time-picker/LocalizedTimePicker.tsx
new file mode 100644
index 00000000000000..74c74c4604b347
--- /dev/null
+++ b/docs/src/pages/components/time-picker/LocalizedTimePicker.tsx
@@ -0,0 +1,58 @@
+import * as React from 'react';
+import frLocale from 'date-fns/locale/fr';
+import ruLocale from 'date-fns/locale/ru';
+import arSaLocale from 'date-fns/locale/ar-SA';
+import enLocale from 'date-fns/locale/en-US';
+import ToggleButton from '@material-ui/core/ToggleButton';
+import ToggleButtonGroup from '@material-ui/core/ToggleButtonGroup';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+
+import TimePicker from '@material-ui/lab/TimePicker';
+
+const localeMap = {
+ en: enLocale,
+ fr: frLocale,
+ ru: ruLocale,
+ ar: arSaLocale,
+};
+
+export default function LocalizedTimePicker() {
+ const [locale, setLocale] = React.useState('ru');
+ const [selectedDate, handleDateChange] = React.useState(
+ new Date(),
+ );
+
+ const selectLocale = (newLocale: any) => {
+ setLocale(newLocale);
+ };
+
+ return (
+
+
+
+ handleDateChange(date)}
+ renderInput={(params) => }
+ />
+
+ {Object.keys(localeMap).map((localeItem) => (
+ selectLocale(localeItem)}
+ >
+ {localeItem}
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/docs/src/pages/components/time-picker/ResponsiveTimePickers.js b/docs/src/pages/components/time-picker/ResponsiveTimePickers.js
new file mode 100644
index 00000000000000..dc77f244a5760c
--- /dev/null
+++ b/docs/src/pages/components/time-picker/ResponsiveTimePickers.js
@@ -0,0 +1,41 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import TimePicker from '@material-ui/lab/TimePicker';
+import MobileTimePicker from '@material-ui/lab/MobileTimePicker';
+import DesktopTimePicker from '@material-ui/lab/DesktopTimePicker';
+
+export default function ResponsiveTimePickers() {
+ const [value, setValue] = React.useState(
+ new Date('2018-01-01T00:00:00.000Z'),
+ );
+
+ return (
+
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+ }
+ />
+
+
+ );
+}
diff --git a/docs/src/pages/components/time-picker/ResponsiveTimePickers.tsx b/docs/src/pages/components/time-picker/ResponsiveTimePickers.tsx
new file mode 100644
index 00000000000000..0552187c375a61
--- /dev/null
+++ b/docs/src/pages/components/time-picker/ResponsiveTimePickers.tsx
@@ -0,0 +1,41 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import TimePicker from '@material-ui/lab/TimePicker';
+import MobileTimePicker from '@material-ui/lab/MobileTimePicker';
+import DesktopTimePicker from '@material-ui/lab/DesktopTimePicker';
+
+export default function ResponsiveTimePickers() {
+ const [value, setValue] = React.useState(
+ new Date('2018-01-01T00:00:00.000Z'),
+ );
+
+ return (
+
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+ }
+ />
+
+
+ );
+}
diff --git a/docs/src/pages/components/time-picker/SecondsTimePicker.js b/docs/src/pages/components/time-picker/SecondsTimePicker.js
new file mode 100644
index 00000000000000..91a5752892c293
--- /dev/null
+++ b/docs/src/pages/components/time-picker/SecondsTimePicker.js
@@ -0,0 +1,41 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import TimePicker from '@material-ui/lab/TimePicker';
+
+export default function SecondsTimePicker() {
+ const [value, setValue] = React.useState(new Date());
+
+ return (
+
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+
+
+ );
+}
diff --git a/docs/src/pages/components/time-picker/SecondsTimePicker.tsx b/docs/src/pages/components/time-picker/SecondsTimePicker.tsx
new file mode 100644
index 00000000000000..c64959595806c8
--- /dev/null
+++ b/docs/src/pages/components/time-picker/SecondsTimePicker.tsx
@@ -0,0 +1,41 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import TimePicker from '@material-ui/lab/TimePicker';
+
+export default function SecondsTimePicker() {
+ const [value, setValue] = React.useState(new Date());
+
+ return (
+
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+
+
+ );
+}
diff --git a/docs/src/pages/components/time-picker/StaticTimePickerDemo.js b/docs/src/pages/components/time-picker/StaticTimePickerDemo.js
new file mode 100644
index 00000000000000..61a317584eadc3
--- /dev/null
+++ b/docs/src/pages/components/time-picker/StaticTimePickerDemo.js
@@ -0,0 +1,22 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import StaticTimePicker from '@material-ui/lab/StaticTimePicker';
+
+export default function StaticTimePickerDemo() {
+ const [value, setValue] = React.useState(new Date());
+
+ return (
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+
+ );
+}
diff --git a/docs/src/pages/components/time-picker/StaticTimePickerDemo.tsx b/docs/src/pages/components/time-picker/StaticTimePickerDemo.tsx
new file mode 100644
index 00000000000000..e0289e25b26f98
--- /dev/null
+++ b/docs/src/pages/components/time-picker/StaticTimePickerDemo.tsx
@@ -0,0 +1,22 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import StaticTimePicker from '@material-ui/lab/StaticTimePicker';
+
+export default function StaticTimePickerDemo() {
+ const [value, setValue] = React.useState(new Date());
+
+ return (
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+
+ );
+}
diff --git a/docs/src/pages/components/time-picker/StaticTimePickerLandscape.js b/docs/src/pages/components/time-picker/StaticTimePickerLandscape.js
new file mode 100644
index 00000000000000..1109b159aa3df6
--- /dev/null
+++ b/docs/src/pages/components/time-picker/StaticTimePickerLandscape.js
@@ -0,0 +1,24 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import StaticTimePicker from '@material-ui/lab/StaticTimePicker';
+
+export default function StaticTimePickerLandscape() {
+ const [value, setValue] = React.useState(new Date());
+
+ return (
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+
+ );
+}
diff --git a/docs/src/pages/components/time-picker/StaticTimePickerLandscape.tsx b/docs/src/pages/components/time-picker/StaticTimePickerLandscape.tsx
new file mode 100644
index 00000000000000..31d4043833e313
--- /dev/null
+++ b/docs/src/pages/components/time-picker/StaticTimePickerLandscape.tsx
@@ -0,0 +1,24 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import StaticTimePicker from '@material-ui/lab/StaticTimePicker';
+
+export default function StaticTimePickerLandscape() {
+ const [value, setValue] = React.useState(new Date());
+
+ return (
+
+ {
+ setValue(newValue);
+ }}
+ renderInput={(params) => }
+ />
+
+ );
+}
diff --git a/docs/src/pages/components/time-picker/TimeValidationTimePicker.js b/docs/src/pages/components/time-picker/TimeValidationTimePicker.js
new file mode 100644
index 00000000000000..fc35465cd28105
--- /dev/null
+++ b/docs/src/pages/components/time-picker/TimeValidationTimePicker.js
@@ -0,0 +1,41 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import TimePicker from '@material-ui/lab/TimePicker';
+
+export default function TimeValidationTimePicker() {
+ const [value, setValue] = React.useState(new Date('2020-01-01 12:00'));
+
+ return (
+
+
+ }
+ value={value}
+ label="min/max time"
+ onChange={(newValue) => {
+ setValue(newValue);
+ }}
+ minTime={new Date(0, 0, 0, 8)}
+ maxTime={new Date(0, 0, 0, 18, 45)}
+ />
+ }
+ label="Disable odd hours"
+ value={value}
+ onChange={(newValue) => {
+ setValue(newValue);
+ }}
+ shouldDisableTime={(timeValue, clockType) => {
+ if (clockType === 'hours' && timeValue % 2) {
+ return true;
+ }
+
+ return false;
+ }}
+ />
+
+
+ );
+}
diff --git a/docs/src/pages/components/time-picker/TimeValidationTimePicker.tsx b/docs/src/pages/components/time-picker/TimeValidationTimePicker.tsx
new file mode 100644
index 00000000000000..c096073a6af2ed
--- /dev/null
+++ b/docs/src/pages/components/time-picker/TimeValidationTimePicker.tsx
@@ -0,0 +1,43 @@
+import * as React from 'react';
+import TextField from '@material-ui/core/TextField';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import TimePicker from '@material-ui/lab/TimePicker';
+
+export default function TimeValidationTimePicker() {
+ const [value, setValue] = React.useState(
+ new Date('2020-01-01 12:00'),
+ );
+
+ return (
+
+
+ }
+ value={value}
+ label="min/max time"
+ onChange={(newValue) => {
+ setValue(newValue);
+ }}
+ minTime={new Date(0, 0, 0, 8)}
+ maxTime={new Date(0, 0, 0, 18, 45)}
+ />
+ }
+ label="Disable odd hours"
+ value={value}
+ onChange={(newValue) => {
+ setValue(newValue);
+ }}
+ shouldDisableTime={(timeValue, clockType) => {
+ if (clockType === 'hours' && timeValue % 2) {
+ return true;
+ }
+
+ return false;
+ }}
+ />
+
+
+ );
+}
diff --git a/docs/src/pages/components/time-picker/time-picker.md b/docs/src/pages/components/time-picker/time-picker.md
new file mode 100644
index 00000000000000..bea749062b1f65
--- /dev/null
+++ b/docs/src/pages/components/time-picker/time-picker.md
@@ -0,0 +1,80 @@
+---
+title: React Time Picker component
+components: TimePicker
+githubLabel: 'component: TimePicker'
+packageName: '@material-ui/lab'
+materialDesign: https://material.io/components/time-pickers
+---
+
+# Time Picker
+
+Time pickers allow the user to select a single time.
+
+Time pickers allow the user to select a single time (in the hours:minutes format).
+The selected time is indicated by the filled circle at the end of the clock hand.
+
+{{"component": "modules/components/ComponentLinkHeader.js"}}
+
+## Requirements
+
+This component relies on the date management library of your choice. It supports [date-fns](https://date-fns.org/), [luxon](https://moment.github.io/luxon/), [dayjs](https://github.com/iamkun/dayjs), [moment](https://momentjs.com/) and any other library via a public `dateAdapter` interface.
+
+Please install any of these libraries and set up the right date engine by wrapping your root (or the highest level you wish the pickers to be available) with `LocalizationProvider`:
+
+```jsx
+// or @material-ui/lab/dateAdapter/{dayjs,luxon,moment} or any valid date-io adapter
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+
+function App() {
+ return (
+
+ ...
+
+ );
+}
+```
+
+## Basic usage
+
+The time picker will automatically adjust to the locale's time setting, i.e. the 12-hour or 24-hour format. This can be controlled with `ampm` prop.
+
+{{"demo": "pages/components/time-picker/BasicTimePicker.js"}}
+
+## Localization
+
+Use `LocalizationProvider` to change the date-engine locale that is used to render the time picker. Note that `am/pm` setting is switched automatically:
+
+{{"demo": "pages/components/time-picker/LocalizedTimePicker.js"}}
+
+## Responsiveness
+
+The time picker component is designed and optimized for the device it runs on.
+
+- The "Mobile" version works best for touch devices and small screens.
+- The "Desktop" version works best for mouse devices and large screens.
+
+By default, the `TimePicker` component uses a `@media (pointer: fine)` media query to determine which version to use.
+This can be customized with the `desktopModeMediaQuery` prop.
+
+{{"demo": "pages/components/time-picker/ResponsiveTimePickers.js"}}
+
+## Time validation
+
+{{"demo": "pages/components/time-picker/TimeValidationTimePicker.js"}}
+
+## Static mode
+
+It's possible to render any picker inline. This will enable building custom popover/modal containers.
+
+{{"demo": "pages/components/time-picker/StaticTimePickerDemo.js", "bg": true}}
+
+## Landscape
+
+{{"demo": "pages/components/time-picker/StaticTimePickerLandscape.js", "bg": true}}
+
+## Seconds
+
+The seconds input can be used for selection of a precise time point.
+
+{{"demo": "pages/components/time-picker/SecondsTimePicker.js"}}
diff --git a/package.json b/package.json
index b0a6f7fba43bdd..ae5142d8dd1a7f 100644
--- a/package.json
+++ b/package.json
@@ -25,13 +25,13 @@
"docs:mdicons:synonyms": "babel-node --config-file ./babel.config.js ./docs/scripts/updateIconSynonyms",
"extract-error-codes": "lerna run --parallel extract-error-codes",
"framer:build": "yarn workspace framer build",
- "jsonlint": "node scripts/jsonlint.js",
+ "jsonlint": "node ./scripts/jsonlint.js",
"lint": "eslint . --cache --report-unused-disable-directives --ext .js,.ts,.tsx",
"lint:ci": "eslint . --report-unused-disable-directives --ext .js,.ts,.tsx",
"stylelint": "stylelint 'docs/**/*.js' 'docs/**/*.ts' 'docs/**/*.tsx'",
"prettier": "node ./scripts/prettier.js",
"prettier:all": "node ./scripts/prettier.js write",
- "size:snapshot": "node scripts/sizeSnapshot/create",
+ "size:snapshot": "node --max-old-space-size=2048 ./scripts/sizeSnapshot/create",
"size:why": "node scripts/sizeSnapshot/why",
"start": "yarn && yarn docs:dev",
"t": "node test/cli.js",
@@ -181,6 +181,7 @@
"nyc": {
"include": [
"packages/material-ui/src/**/*.js",
+ "packages/material-ui/lab/**/*.{ts,tsx}",
"packages/material-ui-utils/src/**/*.js",
"packages/material-ui-styles/src/**/*.js"
],
diff --git a/packages/eslint-plugin-material-ui/README.md b/packages/eslint-plugin-material-ui/README.md
index a9f2ed702e6460..b5e5fd45548798 100644
--- a/packages/eslint-plugin-material-ui/README.md
+++ b/packages/eslint-plugin-material-ui/README.md
@@ -7,6 +7,7 @@ Custom eslint rules for Material-UI.
- `disallow-active-element-as-key-event-target`
- `docgen-ignore-before-comment`
- `no-hardcoded-labels`
+- `lower-case-test-name`
- ~~`restricted-path-imports`~~
### disallow-active-element-as-key-event-target
diff --git a/packages/material-ui-codemod/src/v1.0.0/import-path.test/actual.js b/packages/material-ui-codemod/src/v1.0.0/import-path.test/actual.js
index 4dc0b3f4bf1705..d7ad017d5e7ae8 100644
--- a/packages/material-ui-codemod/src/v1.0.0/import-path.test/actual.js
+++ b/packages/material-ui-codemod/src/v1.0.0/import-path.test/actual.js
@@ -14,11 +14,7 @@ import List, {
ListItemSecondaryAction,
} from '@material-ui/core/List';
import Dialog, { DialogTitle } from '@material-ui/core/Dialog';
-import {
- DialogActions,
- DialogContent,
- DialogContentText,
-} from '@material-ui/core/Dialog';
+import { DialogActions, DialogContent, DialogContentText } from '@material-ui/core/Dialog';
import Slide from '@material-ui/core/transitions/Slide';
import Radio, { RadioGroup } from '@material-ui/core/Radio';
import { FormControlLabel } from '@material-ui/core/Form';
diff --git a/packages/material-ui-lab/package.json b/packages/material-ui-lab/package.json
index debdb2e41bf2ae..73cb6d49fff793 100644
--- a/packages/material-ui-lab/package.json
+++ b/packages/material-ui-lab/package.json
@@ -38,12 +38,28 @@
"peerDependencies": {
"@material-ui/core": "^5.0.0-alpha.11",
"@types/react": "^16.8.6 || ^17.0.0",
+ "date-fns": "^2.0.0",
+ "dayjs": "^1.8.17",
+ "luxon": "^1.21.3",
+ "moment": "^2.24.0",
"react": "^16.8.0 || ^17.0.0",
"react-dom": "^16.8.0 || ^17.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
+ },
+ "date-fns": {
+ "optional": true
+ },
+ "dayjs": {
+ "optional": true
+ },
+ "luxon": {
+ "optional": true
+ },
+ "moment": {
+ "optional": true
}
},
"dependencies": {
@@ -53,10 +69,21 @@
"@material-ui/utils": "^5.0.0-alpha.15",
"clsx": "^1.0.4",
"prop-types": "^15.7.2",
- "react-is": "^16.8.0 || ^17.0.0"
+ "react-is": "^16.8.0 || ^17.0.0",
+ "@date-io/date-fns": "^2.8.0",
+ "@date-io/dayjs": "^2.8.0",
+ "@date-io/luxon": "^2.8.0",
+ "@date-io/moment": "^2.8.0",
+ "react-transition-group": "^4.4.1",
+ "rifm": "^0.12.0"
},
"devDependencies": {
- "@material-ui/types": "^5.1.0"
+ "@material-ui/types": "^5.1.0",
+ "@types/luxon": "^0.5.2",
+ "date-fns": "^2.0.0",
+ "dayjs": "^1.8.17",
+ "luxon": "^1.21.3",
+ "moment": "^2.24.0"
},
"sideEffects": false,
"publishConfig": {
diff --git a/packages/pickers/lib/src/views/Clock/Clock.tsx b/packages/material-ui-lab/src/ClockPicker/Clock.tsx
similarity index 73%
rename from packages/pickers/lib/src/views/Clock/Clock.tsx
rename to packages/material-ui-lab/src/ClockPicker/Clock.tsx
index a6c2d07d76f409..a7a5f783dbf6fb 100644
--- a/packages/pickers/lib/src/views/Clock/Clock.tsx
+++ b/packages/material-ui-lab/src/ClockPicker/Clock.tsx
@@ -1,41 +1,42 @@
import * as React from 'react';
-import * as PropTypes from 'prop-types';
+import PropTypes from 'prop-types';
import clsx from 'clsx';
import IconButton from '@material-ui/core/IconButton';
import Typography from '@material-ui/core/Typography';
-import { makeStyles } from '@material-ui/core/styles';
+import { createStyles, WithStyles, Theme, withStyles } from '@material-ui/core/styles';
import ClockPointer from './ClockPointer';
-import { useUtils } from '../../_shared/hooks/useUtils';
-import { VIEW_HEIGHT } from '../../constants/dimensions';
-import { ClockViewType } from '../../constants/ClockType';
-import { PickerOnChangeFn } from '../../_shared/hooks/useViews';
-import { getHours, getMinutes } from '../../_helpers/time-utils';
-import { useDefaultProps } from '../../_shared/withDefaultProps';
-import { useMeridiemMode } from '../../TimePicker/TimePickerToolbar';
-import { PickerSelectionState } from '../../_shared/hooks/usePickerState';
-import { useGlobalKeyDown, keycode } from '../../_shared/hooks/useKeyDown';
-import { WrapperVariantContext } from '../../wrappers/WrapperVariantContext';
+import { useUtils, MuiPickersAdapter } from '../internal/pickers/hooks/useUtils';
+import { VIEW_HEIGHT } from '../internal/pickers/constants/dimensions';
+import { ClockViewType } from '../internal/pickers/constants/ClockType';
+import { getHours, getMinutes } from '../internal/pickers/time-utils';
+import { useGlobalKeyDown, keycode } from '../internal/pickers/hooks/useKeyDown';
+import {
+ WrapperVariantContext,
+ IsStaticVariantContext,
+} from '../internal/pickers/wrappers/WrapperVariantContext';
+import { PickerSelectionState } from '../internal/pickers/hooks/usePickerState';
+import { useMeridiemMode } from '../internal/pickers/hooks/date-helpers-hooks';
export interface ClockProps extends ReturnType {
date: TDate | null;
type: ClockViewType;
value: number;
isTimeDisabled: (timeValue: number, type: ClockViewType) => boolean;
- children: React.ReactElement[];
- onDateChange: PickerOnChangeFn;
+ children: React.ReactNode[];
onChange: (value: number, isFinish?: PickerSelectionState) => void;
ampm?: boolean;
minutesStep?: number;
ampmInClock?: boolean;
allowKeyboardControl?: boolean;
+ getClockLabelText: (
+ view: 'hours' | 'minutes' | 'seconds',
+ time: TDate,
+ adapter: MuiPickersAdapter,
+ ) => string;
}
-const muiComponentConfig = {
- name: 'MuiPickersClock',
-};
-
-export const useStyles = makeStyles(
- (theme) => ({
+export const styles = (theme: Theme) =>
+ createStyles({
root: {
display: 'flex',
justifyContent: 'center',
@@ -92,16 +93,20 @@ export const useStyles = makeStyles(
backgroundColor: theme.palette.primary.light,
},
},
- }),
- muiComponentConfig
-);
+ });
+
+export type ClockClassKey = keyof WithStyles['classes'];
-export function Clock(props: ClockProps) {
+/**
+ * @ignore - internal component.
+ */
+function Clock(props: ClockProps & WithStyles) {
const {
allowKeyboardControl,
ampm,
ampmInClock = false,
children: numbersElementsArray,
+ classes,
date,
handleMeridiemChange,
isTimeDisabled,
@@ -110,10 +115,11 @@ export function Clock(props: ClockProps) {
onChange,
type,
value,
- } = useDefaultProps(props, muiComponentConfig);
+ getClockLabelText,
+ } = props;
const utils = useUtils();
- const classes = useStyles();
+ const isStatic = React.useContext(IsStaticVariantContext);
const wrapperVariant = React.useContext(WrapperVariantContext);
const isMoving = React.useRef(false);
@@ -138,12 +144,12 @@ export function Clock(props: ClockProps) {
offsetY = e.changedTouches[0].clientY - rect.top;
}
- const value =
+ const newSelectedValue =
type === 'seconds' || type === 'minutes'
? getMinutes(offsetX, offsetY, minutesStep)
: getHours(offsetX, offsetY, Boolean(ampm));
- handleValueChange(value, isFinish);
+ handleValueChange(newSelectedValue, isFinish);
};
const handleTouchMove = (e: React.TouchEvent) => {
@@ -163,6 +169,7 @@ export function Clock(props: ClockProps) {
e.stopPropagation();
// MouseEvent.which is deprecated, but MouseEvent.buttons is not supported in Safari
const isButtonPressed =
+ // tslint:disable-next-line deprecation
typeof e.buttons === 'undefined' ? e.nativeEvent.which === 1 : e.buttons === 1;
if (isButtonPressed) {
@@ -187,21 +194,19 @@ export function Clock(props: ClockProps) {
}, [type, value]);
const keyboardControlStep = type === 'minutes' ? minutesStep : 1;
- useGlobalKeyDown(
- Boolean(allowKeyboardControl ?? wrapperVariant !== 'static') && !isMoving.current,
- {
- [keycode.Home]: () => handleValueChange(0, 'partial'), // annulate both hours and minutes
- [keycode.End]: () => handleValueChange(type === 'minutes' ? 59 : 23, 'partial'),
- [keycode.ArrowUp]: () => handleValueChange(value + keyboardControlStep, 'partial'),
- [keycode.ArrowDown]: () => handleValueChange(value - keyboardControlStep, 'partial'),
- }
- );
+ useGlobalKeyDown(Boolean(allowKeyboardControl ?? !isStatic) && !isMoving.current, {
+ [keycode.Home]: () => handleValueChange(0, 'partial'), // annulate both hours and minutes
+ [keycode.End]: () => handleValueChange(type === 'minutes' ? 59 : 23, 'partial'),
+ [keycode.ArrowUp]: () => handleValueChange(value + keyboardControlStep, 'partial'),
+ [keycode.ArrowDown]: () => handleValueChange(value - keyboardControlStep, 'partial'),
+ });
return (
(props: ClockProps
) {
isInner={isPointerInner}
hasSelected={hasSelected}
aria-live="polite"
- aria-label={`Selected time ${utils.format(date, 'fullTime')}`}
+ aria-label={getClockLabelText(type, date, utils)}
/>
)}
@@ -259,4 +264,6 @@ Clock.propTypes = {
minutesStep: PropTypes.number,
} as any;
-Clock.displayName = 'Clock';
+export default withStyles(styles, {
+ name: 'MuiClock',
+})(Clock) as (props: ClockProps) => JSX.Element;
diff --git a/packages/pickers/lib/src/views/Clock/ClockNumber.tsx b/packages/material-ui-lab/src/ClockPicker/ClockNumber.tsx
similarity index 55%
rename from packages/pickers/lib/src/views/Clock/ClockNumber.tsx
rename to packages/material-ui-lab/src/ClockPicker/ClockNumber.tsx
index aefb8c3e020ba7..8322211d94f7be 100644
--- a/packages/pickers/lib/src/views/Clock/ClockNumber.tsx
+++ b/packages/material-ui-lab/src/ClockPicker/ClockNumber.tsx
@@ -2,10 +2,10 @@ import * as React from 'react';
import clsx from 'clsx';
import Typography from '@material-ui/core/Typography';
import ButtonBase from '@material-ui/core/ButtonBase';
-import { makeStyles, fade } from '@material-ui/core/styles';
-import { onSpaceOrEnter } from '../../_helpers/utils';
-import { useCanAutoFocus } from '../../_shared/hooks/useCanAutoFocus';
-import { PickerSelectionState } from '../../_shared/hooks/usePickerState';
+import { createStyles, WithStyles, withStyles, Theme, alpha } from '@material-ui/core/styles';
+import { onSpaceOrEnter } from '../internal/pickers/utils';
+import { useCanAutoFocus } from '../internal/pickers/hooks/useCanAutoFocus';
+import { PickerSelectionState } from '../internal/pickers/hooks/usePickerState';
const positions: Record = {
0: [0, 40],
@@ -44,44 +44,55 @@ export interface ClockNumberProps {
selected: boolean;
}
-export const useStyles = makeStyles(
- (theme) => {
- const size = 32;
- const clockNumberColor =
- theme.palette.type === 'light' ? theme.palette.text.primary : theme.palette.text.secondary;
+export const styles = (theme: Theme) => {
+ const size = 32;
+ const clockNumberColor =
+ theme.palette.mode === 'light' ? theme.palette.text.primary : theme.palette.text.secondary;
- return {
- root: {
- outline: 0,
- width: size,
- height: size,
- userSelect: 'none',
- position: 'absolute',
- left: `calc((100% - ${size}px) / 2)`,
- display: 'inline-flex',
- justifyContent: 'center',
- alignItems: 'center',
- borderRadius: '50%',
- color: clockNumberColor,
- '&:focused': {
- backgroundColor: theme.palette.background.paper,
- },
- },
- clockNumberSelected: {
- color: theme.palette.primary.contrastText,
+ return createStyles({
+ root: {
+ outline: 0,
+ width: size,
+ height: size,
+ userSelect: 'none',
+ position: 'absolute',
+ left: `calc((100% - ${size}px) / 2)`,
+ display: 'inline-flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ borderRadius: '50%',
+ color: clockNumberColor,
+ '&:focused': {
+ backgroundColor: theme.palette.background.paper,
},
- clockNumberDisabled: {
- pointerEvents: 'none',
- color: fade(clockNumberColor, 0.2),
- },
- };
- },
- { name: 'MuiPickersClockNumber' }
-);
+ },
+ clockNumberSelected: {
+ color: theme.palette.primary.contrastText,
+ },
+ clockNumberDisabled: {
+ pointerEvents: 'none',
+ color: alpha(clockNumberColor, 0.2),
+ },
+ });
+};
+
+export type ClockNumberClassKey = keyof WithStyles['classes'];
+
+/**
+ * @ignore - internal component.
+ */
+const ClockNumber: React.FC> = (props) => {
+ const {
+ disabled,
+ getClockNumberText,
+ index,
+ isInner,
+ label,
+ onSelect,
+ selected,
+ classes,
+ } = props;
-export const ClockNumber: React.FC = (props) => {
- const { disabled, getClockNumberText, index, isInner, label, onSelect, selected } = props;
- const classes = useStyles();
const canAutoFocus = useCanAutoFocus();
const ref = React.useRef(null);
const className = clsx(classes.root, {
@@ -121,4 +132,4 @@ export const ClockNumber: React.FC = (props) => {
);
};
-export default ClockNumber;
+export default withStyles(styles, { name: 'MuiClockNumber' })(ClockNumber);
diff --git a/packages/pickers/lib/src/views/Clock/ClockNumbers.tsx b/packages/material-ui-lab/src/ClockPicker/ClockNumbers.tsx
similarity index 90%
rename from packages/pickers/lib/src/views/Clock/ClockNumbers.tsx
rename to packages/material-ui-lab/src/ClockPicker/ClockNumbers.tsx
index 911459af1b550e..43617104061651 100644
--- a/packages/pickers/lib/src/views/Clock/ClockNumbers.tsx
+++ b/packages/material-ui-lab/src/ClockPicker/ClockNumbers.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
-import { ClockNumber } from './ClockNumber';
-import { MuiPickersAdapter } from '../../_shared/hooks/useUtils';
-import { PickerSelectionState } from '../../_shared/hooks/usePickerState';
+import ClockNumber from './ClockNumber';
+import { MuiPickersAdapter } from '../internal/pickers/hooks/useUtils';
+import { PickerSelectionState } from '../internal/pickers/hooks/usePickerState';
interface GetHourNumbersOptions {
ampm: boolean;
@@ -12,6 +12,9 @@ interface GetHourNumbersOptions {
utils: MuiPickersAdapter;
}
+/**
+ * @ignore - internal component.
+ */
export const getHourNumbers = ({
ampm,
date,
@@ -60,7 +63,7 @@ export const getHourNumbers = ({
label={utils.formatNumber(label)}
onSelect={() => onChange(hour, 'finish')}
getClockNumberText={getClockNumberText}
- />
+ />,
);
}
diff --git a/packages/pickers/lib/src/views/Clock/ClockView.tsx b/packages/material-ui-lab/src/ClockPicker/ClockPicker.tsx
similarity index 56%
rename from packages/pickers/lib/src/views/Clock/ClockView.tsx
rename to packages/material-ui-lab/src/ClockPicker/ClockPicker.tsx
index 1cce82f6c3e6da..8566dfe8b09b28 100644
--- a/packages/pickers/lib/src/views/Clock/ClockView.tsx
+++ b/packages/material-ui-lab/src/ClockPicker/ClockPicker.tsx
@@ -1,63 +1,61 @@
import * as React from 'react';
-import * as PropTypes from 'prop-types';
-import { makeStyles } from '@material-ui/core/styles';
-import { Clock } from './Clock';
-import { pipe } from '../../_helpers/utils';
-import { useUtils, useNow } from '../../_shared/hooks/useUtils';
-import { PickerOnChangeFn } from '../../_shared/hooks/useViews';
-import { useDefaultProps } from '../../_shared/withDefaultProps';
+import PropTypes from 'prop-types';
+import { createStyles, WithStyles, withStyles } from '@material-ui/core/styles';
+import Clock from './Clock';
+import { pipe } from '../internal/pickers/utils';
+import { useUtils, useNow, MuiPickersAdapter } from '../internal/pickers/hooks/useUtils';
import { getHourNumbers, getMinutesNumbers } from './ClockNumbers';
-import { useMeridiemMode } from '../../TimePicker/TimePickerToolbar';
-import { PickerSelectionState } from '../../_shared/hooks/usePickerState';
-import { ArrowSwitcher, ExportedArrowSwitcherProps } from '../../_shared/ArrowSwitcher';
+import ArrowSwitcher, {
+ ExportedArrowSwitcherProps,
+} from '../internal/pickers/PickersArrowSwitcher';
import {
convertValueToMeridiem,
createIsAfterIgnoreDatePart,
TimeValidationProps,
-} from '../../_helpers/time-utils';
+} from '../internal/pickers/time-utils';
+import { PickerOnChangeFn } from '../internal/pickers/hooks/useViews';
+import { PickerSelectionState } from '../internal/pickers/hooks/usePickerState';
+import { useMeridiemMode } from '../internal/pickers/hooks/date-helpers-hooks';
-export interface ExportedClockViewProps extends TimeValidationProps {
+export interface ExportedClockPickerProps extends TimeValidationProps {
/**
* 12h/24h view for hour selection clock.
- *
* @default true
*/
ampm?: boolean;
/**
* Step over minutes.
- *
* @default 1
*/
minutesStep?: number;
/**
* Display ampm controls under the clock (instead of in the toolbar).
- *
* @default false
*/
ampmInClock?: boolean;
/**
* Enables keyboard listener for moving between days in calendar.
- *
* @default currentWrapper !== 'static'
*/
allowKeyboardControl?: boolean;
+ /**
+ * Accessible text that helps user to understand which time and view is selected.
+ * @default (view, time) => `Select ${view}. Selected time is ${format(time, 'fullTime')}`
+ */
+ getClockLabelText?: (
+ view: 'hours' | 'minutes' | 'seconds',
+ time: TDate,
+ adapter: MuiPickersAdapter,
+ ) => string;
}
-export interface ClockViewProps
- extends ExportedClockViewProps,
+export interface ClockPickerProps
+ extends ExportedClockPickerProps,
ExportedArrowSwitcherProps {
/**
* Selected date @DateIOType.
*/
date: TDate | null;
- /**
- * Clock type.
- */
- type: 'hours' | 'minutes' | 'seconds';
- /**
- * On change date without moving between views @DateIOType.
- */
- onDateChange: PickerOnChangeFn;
/**
* On change callback @DateIOType.
*/
@@ -76,39 +74,44 @@ export interface ClockViewProps
getSecondsClockNumberText?: (secondsText: string) => string;
openNextView: () => void;
openPreviousView: () => void;
+ view: 'hours' | 'minutes' | 'seconds';
nextViewAvailable: boolean;
previousViewAvailable: boolean;
showViewSwitcher?: boolean;
}
-const muiPickersComponentConfig = { name: 'MuiPickersClockView' };
+export const styles = createStyles({
+ arrowSwitcher: {
+ position: 'absolute',
+ right: 12,
+ top: 15,
+ },
+});
-export const useStyles = makeStyles(
- () => ({
- arrowSwitcher: {
- position: 'absolute',
- right: 12,
- top: 15,
- },
- }),
- muiPickersComponentConfig
-);
+const getDefaultClockLabelText = (
+ view: 'hours' | 'minutes' | 'seconds',
+ time: TDate,
+ adapter: MuiPickersAdapter,
+) => `Select ${view}. Selected time is ${adapter.format(time, 'fullTime')}`;
-function getMinutesAriaText(minute: string) {
- return `${minute} minutes`;
-}
+const getMinutesAriaText = (minute: string) => `${minute} minutes`;
const getHoursAriaText = (hour: string) => `${hour} hours`;
const getSecondsAriaText = (seconds: string) => `${seconds} seconds`;
-export function ClockView(props: ClockViewProps) {
+/**
+ * @ignore - do not document.
+ */
+function ClockPicker(props: ClockPickerProps & WithStyles) {
const {
allowKeyboardControl,
ampm,
ampmInClock,
+ classes,
date,
disableIgnoringDatePartForTimeValidation,
+ getClockLabelText = getDefaultClockLabelText,
getHoursClockNumberText = getHoursAriaText,
getMinutesClockNumberText = getMinutesAriaText,
getSecondsClockNumberText = getSecondsAriaText,
@@ -120,7 +123,6 @@ export function ClockView(props: ClockViewProps) {
minutesStep = 1,
nextViewAvailable,
onChange,
- onDateChange,
openNextView,
openPreviousView,
previousViewAvailable,
@@ -129,22 +131,17 @@ export function ClockView(props: ClockViewProps) {
rightArrowIcon,
shouldDisableTime,
showViewSwitcher,
- type,
- } = useDefaultProps(props, muiPickersComponentConfig);
+ view,
+ } = props;
const now = useNow();
const utils = useUtils();
- const classes = useStyles();
const dateOrNow = date || now;
- const { meridiemMode, handleMeridiemChange } = useMeridiemMode(
- dateOrNow,
- ampm,
- onDateChange
- );
+ const { meridiemMode, handleMeridiemChange } = useMeridiemMode(dateOrNow, ampm, onChange);
const isTimeDisabled = React.useCallback(
- (rawValue: number, type: 'hours' | 'minutes' | 'seconds') => {
+ (rawValue: number, viewType: 'hours' | 'minutes' | 'seconds') => {
if (date === null) {
return false;
}
@@ -152,25 +149,25 @@ export function ClockView(props: ClockViewProps) {
const validateTimeValue = (getRequestedTimePoint: (when: 'start' | 'end') => TDate) => {
const isAfterComparingFn = createIsAfterIgnoreDatePart(
Boolean(disableIgnoringDatePartForTimeValidation),
- utils
+ utils,
);
return Boolean(
(minTime && isAfterComparingFn(minTime, getRequestedTimePoint('end'))) ||
(maxTime && isAfterComparingFn(getRequestedTimePoint('start'), maxTime)) ||
- (shouldDisableTime && shouldDisableTime(rawValue, type))
+ (shouldDisableTime && shouldDisableTime(rawValue, viewType)),
);
};
- switch (type) {
+ switch (viewType) {
case 'hours': {
const hoursWithMeridiem = convertValueToMeridiem(rawValue, meridiemMode, Boolean(ampm));
return validateTimeValue((when: 'start' | 'end') =>
pipe(
(currentDate) => utils.setHours(currentDate, hoursWithMeridiem),
(dateWithHours) => utils.setMinutes(dateWithHours, when === 'start' ? 0 : 59),
- (dateWithMinutes) => utils.setSeconds(dateWithMinutes, when === 'start' ? 0 : 59)
- )(date)
+ (dateWithMinutes) => utils.setSeconds(dateWithMinutes, when === 'start' ? 0 : 59),
+ )(date),
);
}
@@ -178,8 +175,8 @@ export function ClockView(props: ClockViewProps) {
return validateTimeValue((when: 'start' | 'end') =>
pipe(
(currentDate) => utils.setMinutes(currentDate, rawValue),
- (dateWithMinutes) => utils.setSeconds(dateWithMinutes, when === 'start' ? 0 : 59)
- )(date)
+ (dateWithMinutes) => utils.setSeconds(dateWithMinutes, when === 'start' ? 0 : 59),
+ )(date),
);
case 'seconds':
@@ -198,11 +195,11 @@ export function ClockView(props: ClockViewProps) {
minTime,
shouldDisableTime,
utils,
- ]
+ ],
);
const viewProps = React.useMemo(() => {
- switch (type) {
+ switch (view) {
case 'hours': {
const handleHoursChange = (value: number, isFinish?: PickerSelectionState) => {
const valueWithMeridiem = convertValueToMeridiem(value, meridiemMode, Boolean(ampm));
@@ -265,7 +262,7 @@ export function ClockView(props: ClockViewProps) {
throw new Error('You must provide the type for ClockView');
}
}, [
- type,
+ view,
utils,
date,
ampm,
@@ -296,13 +293,13 @@ export function ClockView(props: ClockViewProps) {
/>
)}
-
date={date}
ampmInClock={ampmInClock}
- // @ts-expect-error FIX ME
- onDateChange={onDateChange}
- type={type}
+ type={view}
ampm={ampm}
+ // @ts-expect-error TODO figure out this weird error
+ getClockLabelText={getClockLabelText}
minutesStep={minutesStep}
allowKeyboardControl={allowKeyboardControl}
isTimeDisabled={isTimeDisabled}
@@ -314,12 +311,130 @@ export function ClockView(props: ClockViewProps) {
);
}
-ClockView.propTypes = {
+(ClockPicker as any).propTypes = {
+ // ----------------------------- Warning --------------------------------
+ // | These PropTypes are generated from the TypeScript type definitions |
+ // | To update them edit TypeScript types and run "yarn proptypes" |
+ // ----------------------------------------------------------------------
+ /**
+ * Enables keyboard listener for moving between days in calendar.
+ * @default currentWrapper !== 'static'
+ */
+ allowKeyboardControl: PropTypes.bool,
+ /**
+ * 12h/24h view for hour selection clock.
+ * @default true
+ */
ampm: PropTypes.bool,
- date: PropTypes.object,
+ /**
+ * Display ampm controls under the clock (instead of in the toolbar).
+ * @default false
+ */
+ ampmInClock: PropTypes.bool,
+ /**
+ * @ignore
+ */
+ classes: PropTypes.object.isRequired,
+ /**
+ * Selected date @DateIOType.
+ */
+ date: PropTypes.any,
+ /**
+ * Do not ignore date part when validating min/max time.
+ * @default false
+ */
+ disableIgnoringDatePartForTimeValidation: PropTypes.bool,
+ /**
+ * Accessible text that helps user to understand which time and view is selected.
+ * @default (view, time) => `Select ${view}. Selected time is ${format(time, 'fullTime')}`
+ */
+ getClockLabelText: PropTypes.func,
+ /**
+ * Get clock number aria-text for hours.
+ */
+ getHoursClockNumberText: PropTypes.func,
+ /**
+ * Get clock number aria-text for minutes.
+ */
+ getMinutesClockNumberText: PropTypes.func,
+ /**
+ * Get clock number aria-text for seconds.
+ */
+ getSecondsClockNumberText: PropTypes.func,
+ /**
+ * Props to pass to left arrow button.
+ */
+ leftArrowButtonProps: PropTypes.object,
+ /**
+ * Left arrow icon aria-label text.
+ */
+ leftArrowButtonText: PropTypes.string,
+ /**
+ * Left arrow icon.
+ */
+ leftArrowIcon: PropTypes.node,
+ /**
+ * Max time acceptable time.
+ * For input validation date part of passed object will be ignored if `disableIgnoringDatePartForTimeValidation` not specified.
+ */
+ maxTime: PropTypes.any,
+ /**
+ * Min time acceptable time.
+ * For input validation date part of passed object will be ignored if `disableIgnoringDatePartForTimeValidation` not specified.
+ */
+ minTime: PropTypes.any,
+ /**
+ * Step over minutes.
+ * @default 1
+ */
minutesStep: PropTypes.number,
+ /**
+ * @ignore
+ */
+ nextViewAvailable: PropTypes.bool.isRequired,
+ /**
+ * On change callback @DateIOType.
+ */
onChange: PropTypes.func.isRequired,
- type: PropTypes.oneOf(['minutes', 'hours', 'seconds']).isRequired,
-} as any;
+ /**
+ * @ignore
+ */
+ openNextView: PropTypes.func.isRequired,
+ /**
+ * @ignore
+ */
+ openPreviousView: PropTypes.func.isRequired,
+ /**
+ * @ignore
+ */
+ previousViewAvailable: PropTypes.bool.isRequired,
+ /**
+ * Props to pass to right arrow button.
+ */
+ rightArrowButtonProps: PropTypes.object,
+ /**
+ * Right arrow icon aria-label text.
+ */
+ rightArrowButtonText: PropTypes.string,
+ /**
+ * Right arrow icon.
+ */
+ rightArrowIcon: PropTypes.node,
+ /**
+ * Dynamically check if time is disabled or not.
+ * If returns `false` appropriate time point will ot be acceptable.
+ */
+ shouldDisableTime: PropTypes.func,
+ /**
+ * @ignore
+ */
+ showViewSwitcher: PropTypes.bool,
+ /**
+ * @ignore
+ */
+ view: PropTypes.oneOf(['hours', 'minutes', 'seconds']).isRequired,
+};
-ClockView.displayName = 'ClockView';
+export default withStyles(styles, { name: 'MuiPickersClockView' })(ClockPicker) as (
+ props: ClockPickerProps,
+) => JSX.Element;
diff --git a/packages/material-ui-lab/src/ClockPicker/ClockPickerStandalone.test.tsx b/packages/material-ui-lab/src/ClockPicker/ClockPickerStandalone.test.tsx
new file mode 100644
index 00000000000000..c47f8b3c75b292
--- /dev/null
+++ b/packages/material-ui-lab/src/ClockPicker/ClockPickerStandalone.test.tsx
@@ -0,0 +1,22 @@
+import * as React from 'react';
+import { createMount, describeConformance } from 'test/utils';
+import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
+import DateFnsAdapter from '@material-ui/lab/dateAdapter/date-fns';
+import ClockPicker from '@material-ui/lab/ClockPicker';
+
+describe(' ', () => {
+ const mount = createMount();
+
+ const localizedMount = (node: React.ReactNode) => {
+ return mount({node} );
+ };
+
+ describeConformance( {}} />, () => ({
+ classes: {},
+ inheritComponent: 'div',
+ mount: localizedMount,
+ refInstanceof: window.HTMLDivElement,
+ // cannot test reactTestRenderer because of required context
+ skip: ['componentProp', 'propsSpread', 'reactTestRenderer'],
+ }));
+});
diff --git a/packages/material-ui-lab/src/ClockPicker/ClockPickerStandalone.tsx b/packages/material-ui-lab/src/ClockPicker/ClockPickerStandalone.tsx
new file mode 100644
index 00000000000000..e08b6f3f961700
--- /dev/null
+++ b/packages/material-ui-lab/src/ClockPicker/ClockPickerStandalone.tsx
@@ -0,0 +1,62 @@
+import * as React from 'react';
+import ClockPicker, { ClockPickerProps } from './ClockPicker';
+import { TimePickerView } from '../internal/pickers/typings/Views';
+import PickerView from '../internal/pickers/Picker/PickerView';
+import { useViews } from '../internal/pickers/hooks/useViews';
+
+export interface ClockPickerStandaloneProps
+ extends Omit<
+ ClockPickerProps,
+ 'view' | 'openNextView' | 'openPreviousView' | 'nextViewAvailable' | 'previousViewAvailable'
+ > {
+ /** Controlled clock view. */
+ view?: TimePickerView;
+ /** Available views for clock. */
+ views?: TimePickerView[];
+ /** Callback fired on view change. */
+ onViewChange?: (view: TimePickerView) => void;
+ /** Initially opened view. */
+ openTo?: TimePickerView;
+ className?: string;
+}
+
+/**
+ * Wrapping public API for better standalone usage of './ClockPicker'
+ * @ignore - internal component.
+ */
+export default React.forwardRef(function ClockPickerStandalone(
+ props: ClockPickerStandaloneProps,
+ ref: React.Ref,
+) {
+ const {
+ view,
+ openTo,
+ className,
+ onViewChange,
+ views = ['hours', 'minutes'] as TimePickerView[],
+ ...other
+ } = props;
+
+ const { openView, setOpenView, nextView, previousView } = useViews({
+ view,
+ views,
+ openTo,
+ onViewChange,
+ onChange: other.onChange,
+ });
+
+ return (
+
+ setOpenView(nextView)}
+ openPreviousView={() => setOpenView(previousView)}
+ {...other}
+ />
+
+ );
+}) as (
+ props: ClockPickerStandaloneProps & React.RefAttributes,
+) => JSX.Element;
diff --git a/packages/pickers/lib/src/views/Clock/ClockPointer.tsx b/packages/material-ui-lab/src/ClockPicker/ClockPointer.tsx
similarity index 86%
rename from packages/pickers/lib/src/views/Clock/ClockPointer.tsx
rename to packages/material-ui-lab/src/ClockPicker/ClockPointer.tsx
index 0cb7f0c2d84f06..9a39a6e35de36d 100644
--- a/packages/pickers/lib/src/views/Clock/ClockPointer.tsx
+++ b/packages/material-ui-lab/src/ClockPicker/ClockPointer.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import clsx from 'clsx';
import { withStyles, createStyles, Theme, WithStyles } from '@material-ui/core/styles';
-import { ClockViewType } from '../../constants/ClockType';
+import { ClockViewType } from '../internal/pickers/constants/ClockType';
export const styles = (theme: Theme) =>
createStyles({
@@ -32,6 +32,8 @@ export const styles = (theme: Theme) =>
},
});
+export type ClockPointerClassKey = keyof WithStyles['classes'];
+
export interface ClockPointerProps
extends React.HTMLProps,
WithStyles {
@@ -41,10 +43,13 @@ export interface ClockPointerProps
type: ClockViewType;
}
+/**
+ * @ignore - internal component.
+ */
class ClockPointer extends React.Component {
- public static getDerivedStateFromProps = (
+ static getDerivedStateFromProps = (
nextProps: ClockPointerProps,
- state: ClockPointer['state']
+ state: ClockPointer['state'],
) => {
if (nextProps.type !== state.previousType) {
return {
@@ -59,13 +64,13 @@ class ClockPointer extends React.Component {
};
};
- public state = {
+ state = {
toAnimateTransform: false,
// eslint-disable-next-line react/no-unused-state
previousType: undefined,
};
- public getAngleStyle = () => {
+ getAngleStyle = () => {
const { value, isInner, type } = this.props;
const max = type === 'hours' ? 12 : 60;
@@ -81,7 +86,7 @@ class ClockPointer extends React.Component {
};
};
- public render() {
+ render() {
const { classes, hasSelected, isInner, type, value, ...other } = this.props;
return (
@@ -103,5 +108,5 @@ class ClockPointer extends React.Component {
}
export default withStyles(styles, {
- name: 'MuiPickersClockPointer',
-})(ClockPointer as React.ComponentType);
+ name: 'MuiClockPointer',
+})(ClockPointer);
diff --git a/packages/material-ui-lab/src/ClockPicker/index.ts b/packages/material-ui-lab/src/ClockPicker/index.ts
new file mode 100644
index 00000000000000..aa84e211e250fe
--- /dev/null
+++ b/packages/material-ui-lab/src/ClockPicker/index.ts
@@ -0,0 +1,5 @@
+export { default } from './ClockPickerStandalone';
+
+export type ClockPickerProps = import('./ClockPickerStandalone').ClockPickerStandaloneProps<
+ TDate
+>;
diff --git a/packages/material-ui-lab/src/DatePicker/DatePicker.spec.tsx b/packages/material-ui-lab/src/DatePicker/DatePicker.spec.tsx
new file mode 100644
index 00000000000000..1178f50e368e8d
--- /dev/null
+++ b/packages/material-ui-lab/src/DatePicker/DatePicker.spec.tsx
@@ -0,0 +1,112 @@
+import * as React from 'react';
+import moment, { Moment } from 'moment';
+import { DatePicker, StaticDatePicker, DayPicker, PickersDay } from '@material-ui/lab';
+import DateFnsAdapter from '../dateAdapter/date-fns';
+import MomentAdapter from '../dateAdapter/moment';
+
+// Allows to set date type right with generic JSX syntax
+
+ value={new Date()}
+ onChange={(date) => date?.getDate()}
+ renderInput={() => }
+/>;
+
+// Throws error if passed value is invalid
+
+ // @ts-expect-error Value is invalid
+ value={moment()}
+ onChange={(date) => date?.getDate()}
+ renderInput={() => }
+/>;
+
+// Inference from the state
+const InferTest = () => {
+ const [date, setDate] = React.useState(moment());
+
+ return (
+ setDate(date)} renderInput={() => } />
+ );
+};
+
+// Infer value type from the dateAdapter
+ console.log(date)}
+ renderInput={() => }
+ dateAdapter={new MomentAdapter()}
+/>;
+
+// Conflict between value type and date adapter causes error
+ console.log(date)}
+ renderInput={() => }
+ // @ts-expect-error
+ dateAdapter={new DateFnsAdapter()}
+/>;
+
+// Conflict between explicit generic type and date adapter causes error
+
+ value={moment()}
+ onChange={(date) => console.log(date)}
+ renderInput={() => }
+ // @ts-expect-error
+ dateAdapter={new LuxonAdapter()}
+/>;
+
+// Allows inferring for side props
+ {day.format('D')} }
+ onChange={(date) => date?.set({ second: 0 })}
+ renderInput={() => }
+/>;
+
+// External components are generic as well
+
+ view="date"
+ views={['date']}
+ date={moment()}
+ minDate={moment()}
+ maxDate={moment()}
+ onChange={(date) => date?.format()}
+/>;
+
+
+ day={new Date()}
+ allowSameDateSelection
+ outsideCurrentMonth
+ onDaySelect={(date) => date?.getDay()}
+/>;
+
+// Edge case and known issue. When the passed `value` is not a date type
+// We cannot infer the type properly without explicit generic type or `dateAdapter` prop
+// So in this case it is expected that type will be the type of `value` as for now
+
+ // getDate is never
+ // @ts-expect-error
+ date?.getDate()
+ }
+ renderInput={() => }
+/>;
+
+{
+ // Allows to pass the wrapper-specific props only to the proper wrapper
+ date?.getDate()}
+ renderInput={() => }
+ displayStaticWrapperAs="desktop"
+ />;
+
+ date?.getDate()}
+ renderInput={() => }
+ // @ts-expect-error
+ displayStaticWrapperAs="desktop"
+ />;
+}
diff --git a/packages/material-ui-lab/src/DatePicker/DatePicker.test.tsx b/packages/material-ui-lab/src/DatePicker/DatePicker.test.tsx
new file mode 100644
index 00000000000000..6248539d0055ce
--- /dev/null
+++ b/packages/material-ui-lab/src/DatePicker/DatePicker.test.tsx
@@ -0,0 +1,521 @@
+import * as React from 'react';
+import { expect } from 'chai';
+import { spy } from 'sinon';
+import TextField from '@material-ui/core/TextField';
+import { fireEvent, screen, waitFor } from 'test/utils';
+import PickersDay from '@material-ui/lab/PickersDay';
+import CalendarSkeleton from '@material-ui/lab/PickersCalendarSkeleton';
+import DatePicker from '@material-ui/lab/DatePicker';
+import MobileDatePicker from '@material-ui/lab/MobileDatePicker';
+import DesktopDatePicker from '@material-ui/lab/DesktopDatePicker';
+import StaticDatePicker from '@material-ui/lab/StaticDatePicker';
+import {
+ createPickerRender,
+ FakeTransitionComponent,
+ adapterToUse,
+ getByMuiTest,
+ getAllByMuiTest,
+ queryAllByMuiTest,
+ openDesktopPicker,
+ openMobilePicker,
+} from '../internal/pickers/test-utils';
+
+describe(' ', () => {
+ const render = createPickerRender({ strict: false });
+
+ // TODO
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip('render proper month', () => {
+ render(
+ {}}
+ renderInput={(params) => }
+ />,
+ );
+
+ expect(screen.getByText('January')).toBeVisible();
+ expect(screen.getByText('2019')).toBeVisible();
+ expect(getAllByMuiTest('day')).to.have.length(31);
+ });
+
+ // TODO
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip('desktop Mode – Accepts date on day button click', () => {
+ const onChangeMock = spy();
+
+ render(
+ }
+ />,
+ );
+
+ openDesktopPicker();
+
+ fireEvent.click(screen.getByLabelText('Jan 2, 2019'));
+ expect(onChangeMock.callCount).to.equal(1);
+
+ expect(screen.queryByRole('dialog')).to.equal(null);
+ });
+
+ // TODO
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip('mobile mode – Accepts date on `OK` button click', () => {
+ const onChangeMock = spy();
+ render(
+ }
+ />,
+ );
+
+ openMobilePicker();
+
+ fireEvent.click(screen.getByLabelText('Jan 2, 2019'));
+ expect(onChangeMock.callCount).to.equal(1);
+ expect(screen.queryByRole('dialog')).not.to.equal(null);
+
+ fireEvent.click(screen.getByText(/ok/i));
+ // TODO revisit calling onChange twice. Now it is expected for mobile mode.
+ expect(onChangeMock.callCount).to.equal(2);
+ expect(screen.queryByRole('dialog')).to.equal(null);
+ });
+
+ // TODO
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip('switches between months', () => {
+ render(
+ {}}
+ renderInput={(params) => }
+ />,
+ );
+
+ expect(getByMuiTest('calendar-month-text')).to.have.text('January');
+
+ fireEvent.click(screen.getByLabelText('next month'));
+ fireEvent.click(screen.getByLabelText('next month'));
+
+ fireEvent.click(screen.getByLabelText('previous month'));
+ fireEvent.click(screen.getByLabelText('previous month'));
+ fireEvent.click(screen.getByLabelText('previous month'));
+
+ expect(getByMuiTest('calendar-month-text')).to.have.text('December');
+ });
+
+ // TODO
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip('selects the closest enabled date if selected date is disabled', () => {
+ const onChangeMock = spy();
+
+ render(
+ }
+ maxDate={adapterToUse.date('2018-01-01T00:00:00.000')}
+ />,
+ );
+
+ expect(getByMuiTest('calendar-year-text')).to.have.text('2018');
+ expect(getByMuiTest('calendar-month-text')).to.have.text('January');
+
+ // onChange must be dispatched with newly selected date
+ expect(onChangeMock.calledWith(adapterToUse.date('2018-01-01T00:00:00.000'))).to.be.equal(true);
+ });
+
+ it('allows to change only year', () => {
+ const onChangeMock = spy();
+ render(
+ }
+ />,
+ );
+
+ fireEvent.click(screen.getByLabelText(/switch to year view/i));
+ fireEvent.click(screen.getByText('2010', { selector: 'button' }));
+
+ expect(getByMuiTest('calendar-year-text')).to.have.text('2010');
+ expect(onChangeMock.callCount).to.equal(1);
+ });
+
+ // TODO
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip('allows to select edge years from list', () => {
+ render(
+ {}}
+ openTo="year"
+ minDate={new Date('2000-01-01T00:00:00.000')}
+ maxDate={new Date('2010-01-01T00:00:00.000')}
+ renderInput={(params) => }
+ />,
+ );
+
+ fireEvent.click(screen.getByText('2010', { selector: 'button' }));
+ expect(getByMuiTest('datepicker-toolbar-date')).to.have.text('Fri, Jan 1');
+ });
+
+ // TODO
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip("doesn't close picker on selection in Mobile mode", () => {
+ render(
+ {}}
+ renderInput={(params) => }
+ />,
+ );
+
+ fireEvent.click(screen.getByRole('textbox'));
+ fireEvent.click(screen.getByLabelText('Jan 2, 2018'));
+
+ expect(screen.queryByRole('dialog')).toBeVisible();
+ });
+
+ // TODO
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip('closes picker on selection in Desktop mode', async () => {
+ render(
+ {}}
+ renderInput={(params) => }
+ />,
+ );
+
+ fireEvent.click(screen.getByLabelText('Choose date, selected date is Jan 1, 2018'));
+
+ await waitFor(() => screen.getByRole('dialog'));
+ fireEvent.click(screen.getByLabelText('Jan 2, 2018'));
+
+ expect(screen.queryByRole('dialog')).to.equal(null);
+ });
+
+ it('prop `clearable` - renders clear button in Mobile mode', () => {
+ const onChangeMock = spy();
+ render(
+ }
+ />,
+ );
+
+ openMobilePicker();
+ fireEvent.click(screen.getByText('Clear'));
+
+ expect(onChangeMock.calledWith(null)).to.be.equal(true);
+ expect(screen.queryByRole('dialog')).to.equal(null);
+ });
+
+ // TODO
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip("prop `disableCloseOnSelect` – if `true` doesn't close picker", () => {
+ render(
+ {}}
+ renderInput={(params) => }
+ />,
+ );
+
+ openDesktopPicker();
+ fireEvent.click(screen.getByLabelText('Jan 2, 2018'));
+
+ expect(screen.queryByRole('dialog')).toBeVisible();
+ });
+
+ // TODO
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip('does not call onChange if same date selected', async () => {
+ const onChangeMock = spy();
+
+ render(
+ }
+ />,
+ );
+
+ fireEvent.click(screen.getByLabelText('Choose date, selected date is Jan 1, 2018'));
+ await waitFor(() => screen.getByRole('dialog'));
+
+ fireEvent.click(screen.getByLabelText('Jan 1, 2018'));
+ expect(onChangeMock.callCount).to.equal(0);
+ });
+
+ it('allows to change selected date from the input according to `format`', () => {
+ const onChangeMock = spy();
+ render(
+ }
+ label="Masked input"
+ inputFormat="dd/MM/yyyy"
+ value={new Date('2018-01-01T00:00:00.000Z')}
+ onChange={onChangeMock}
+ InputAdornmentProps={{
+ disableTypography: true,
+ }}
+ />,
+ );
+
+ fireEvent.change(screen.getByRole('textbox'), {
+ target: {
+ value: '10/11/2018',
+ },
+ });
+
+ expect(screen.getByRole('textbox')).to.have.value('10/11/2018');
+ expect(onChangeMock.callCount).to.equal(1);
+ });
+
+ it('prop `showToolbar` – renders toolbar in desktop mode', () => {
+ render(
+ {}}
+ TransitionComponent={FakeTransitionComponent}
+ value={adapterToUse.date('2018-01-01T00:00:00.000')}
+ renderInput={(params) => }
+ />,
+ );
+
+ expect(getByMuiTest('picker-toolbar')).toBeVisible();
+ });
+
+ it('prop `toolbarTitle` – should render title from the prop', () => {
+ render(
+ }
+ open
+ toolbarTitle="test"
+ label="something"
+ onChange={() => {}}
+ value={adapterToUse.date('2018-01-01T00:00:00.000')}
+ />,
+ );
+
+ expect(getByMuiTest('picker-toolbar-title').textContent).to.equal('test');
+ });
+
+ it('prop `toolbarTitle` – should use label if no toolbar title', () => {
+ render(
+ {}}
+ renderInput={(params) => }
+ value={adapterToUse.date('2018-01-01T00:00:00.000')}
+ />,
+ );
+
+ expect(getByMuiTest('picker-toolbar-title').textContent).to.equal('Default label');
+ });
+
+ // TODO
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip('prop `toolbarFormat` – should format toolbar according to passed format', () => {
+ render(
+ }
+ open
+ onChange={() => {}}
+ toolbarFormat="MMMM"
+ value={adapterToUse.date('2018-01-01T00:00:00.000')}
+ />,
+ );
+
+ expect(getByMuiTest('datepicker-toolbar-date').textContent).to.equal('January');
+ });
+
+ it('prop `showTodayButton` – accept current date when "today" button is clicked', () => {
+ const onCloseMock = spy();
+ const onChangeMock = spy();
+ render(
+ }
+ showTodayButton
+ cancelText="stream"
+ onClose={onCloseMock}
+ onChange={onChangeMock}
+ value={adapterToUse.date('2018-01-01T00:00:00.000Z')}
+ DialogProps={{ TransitionComponent: FakeTransitionComponent }}
+ />,
+ );
+
+ fireEvent.click(screen.getByRole('textbox'));
+ fireEvent.click(screen.getByText(/today/i));
+
+ expect(onCloseMock.callCount).to.equal(1);
+ expect(onChangeMock.callCount).to.equal(1);
+ });
+
+ it('ref - should forwardRef to text field', () => {
+ const Component = () => {
+ const ref = React.useRef(null);
+ const focusPicker = () => {
+ if (ref.current) {
+ ref.current.focus();
+ expect(ref.current.id).to.equal('test-focusing-picker');
+ } else {
+ throw new Error('Ref must be available');
+ }
+ };
+
+ return (
+
+ {}}
+ renderInput={(params) => }
+ />
+
+ test
+
+
+ );
+ };
+
+ render( );
+ fireEvent.click(screen.getByText('test'));
+ });
+
+ it('prop `shouldDisableYear` – disables years dynamically', () => {
+ render(
+ }
+ openTo="year"
+ onChange={() => {}}
+ // getByRole() with name attribute is too slow, so restrict the number of rendered years
+ minDate={new Date('2025-01-01T00:00:00.000')}
+ maxDate={new Date('2035-01-01T00:00:00.000')}
+ value={adapterToUse.date('2018-01-01T00:00:00.000Z')}
+ shouldDisableYear={(year) => adapterToUse.getYear(year) === 2030}
+ />,
+ );
+
+ const getYearButton = (year: number) =>
+ screen.getByText(year.toString(), { selector: 'button' });
+
+ expect(getYearButton(2029)).not.to.have.attribute('disabled');
+ expect(getYearButton(2030)).to.have.attribute('disabled');
+ expect(getYearButton(2031)).not.to.have.attribute('disabled');
+ });
+
+ it('prop `onMonthChange` – dispatches callback when months switching', () => {
+ const onMonthChangeMock = spy();
+ render(
+ }
+ onChange={() => {}}
+ onMonthChange={onMonthChangeMock}
+ value={adapterToUse.date('2018-01-01T00:00:00.000Z')}
+ />,
+ );
+
+ fireEvent.click(screen.getByLabelText('next month'));
+ expect(onMonthChangeMock.callCount).to.equal(1);
+ });
+
+ it('prop `loading` – displays default loading indicator', () => {
+ render(
+ }
+ onChange={() => {}}
+ value={adapterToUse.date('2018-01-01T00:00:00.000Z')}
+ />,
+ );
+
+ expect(queryAllByMuiTest(document.body, 'day')).to.have.length(0);
+ expect(getByMuiTest('loading-progress')).toBeVisible();
+ });
+
+ it('prop `renderLoading` – displays custom loading indicator', () => {
+ render(
+ }
+ open
+ onChange={() => {}}
+ renderInput={(params) => }
+ value={adapterToUse.date('2018-01-01T00:00:00.000Z')}
+ />,
+ );
+
+ expect(screen.queryByTestId('loading-progress')).to.equal(null);
+ expect(screen.getByTestId('custom-loading')).toBeVisible();
+ });
+
+ it('prop `ToolbarComponent` – render custom toolbar component', () => {
+ render(
+ }
+ open
+ value={new Date()}
+ onChange={() => {}}
+ ToolbarComponent={() =>
}
+ />,
+ );
+
+ expect(screen.getByTestId('custom-toolbar')).toBeVisible();
+ });
+
+ it('prop `renderDay` – renders custom day', () => {
+ render(
+ }
+ open
+ value={adapterToUse.date('2018-01-01T00:00:00.000')}
+ onChange={() => {}}
+ renderDay={(day, _selected, DayComponentProps) => (
+
+ )}
+ />,
+ );
+
+ expect(screen.getAllByTestId('test-day')).to.have.length(31);
+ });
+
+ // TODO
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip('prop `defaultCalendarMonth` – opens on provided month if date is `null`', () => {
+ render(
+ }
+ open
+ value={null}
+ onChange={() => {}}
+ defaultCalendarMonth={new Date('2018-07-01T00:00:00.000')}
+ />,
+ );
+
+ expect(screen.getByText('July')).toBeVisible();
+ });
+});
diff --git a/packages/material-ui-lab/src/DatePicker/DatePicker.tsx b/packages/material-ui-lab/src/DatePicker/DatePicker.tsx
new file mode 100644
index 00000000000000..655c7aa73b0bb7
--- /dev/null
+++ b/packages/material-ui-lab/src/DatePicker/DatePicker.tsx
@@ -0,0 +1,298 @@
+import PropTypes from 'prop-types';
+import { useUtils } from '../internal/pickers/hooks/useUtils';
+import DatePickerToolbar from './DatePickerToolbar';
+import type { WithViewsProps } from '../internal/pickers/Picker/SharedPickerProps';
+import { ResponsiveWrapper } from '../internal/pickers/wrappers/ResponsiveWrapper';
+import {
+ useParsedDate,
+ OverrideParsableDateProps,
+} from '../internal/pickers/hooks/date-helpers-hooks';
+import type { ExportedDayPickerProps } from '../DayPicker/DayPicker';
+import { MobileWrapper, SomeWrapper } from '../internal/pickers/wrappers/Wrapper';
+import { makeValidationHook, ValidationProps } from '../internal/pickers/hooks/useValidation';
+import {
+ ParsableDate,
+ defaultMinDate,
+ defaultMaxDate,
+} from '../internal/pickers/constants/prop-types';
+import {
+ makePickerWithStateAndWrapper,
+ AllPickerProps,
+ SharedPickerProps,
+} from '../internal/pickers/Picker/makePickerWithState';
+import {
+ getFormatAndMaskByViews,
+ DateValidationError,
+ validateDate,
+} from '../internal/pickers/date-utils';
+
+export type DatePickerView = 'year' | 'date' | 'month';
+
+export interface BaseDatePickerProps
+ extends WithViewsProps<'year' | 'date' | 'month'>,
+ ValidationProps,
+ OverrideParsableDateProps, 'minDate' | 'maxDate'> {}
+
+export const datePickerConfig = {
+ useValidation: makeValidationHook<
+ DateValidationError,
+ ParsableDate,
+ BaseDatePickerProps
+ >(validateDate),
+ DefaultToolbarComponent: DatePickerToolbar,
+ useInterceptProps: ({
+ openTo = 'date',
+ views = ['year', 'date'],
+ minDate: __minDate = defaultMinDate,
+ maxDate: __maxDate = defaultMaxDate,
+ ...other
+ }: AllPickerProps>) => {
+ const utils = useUtils();
+ const minDate = useParsedDate(__minDate);
+ const maxDate = useParsedDate(__maxDate);
+
+ return {
+ views,
+ openTo,
+ minDate,
+ maxDate,
+ ...getFormatAndMaskByViews(views, utils),
+ ...other,
+ };
+ },
+};
+
+export type DatePickerGenericComponent = (
+ props: BaseDatePickerProps & SharedPickerProps,
+) => JSX.Element;
+
+/**
+ * @ignore - do not document.
+ */
+/* @GeneratePropTypes */
+const DatePicker = makePickerWithStateAndWrapper>(ResponsiveWrapper, {
+ name: 'MuiDatePicker',
+ ...datePickerConfig,
+}) as DatePickerGenericComponent;
+
+(DatePicker as any).propTypes = {
+ // ----------------------------- Warning --------------------------------
+ // | These PropTypes are generated from the TypeScript type definitions |
+ // | To update them edit TypeScript types and run "yarn proptypes" |
+ // ----------------------------------------------------------------------
+ /**
+ * Regular expression to detect "accepted" symbols.
+ * @default /\dap/gi
+ */
+ acceptRegex: PropTypes.instanceOf(RegExp),
+ /**
+ * "CANCEL" Text message
+ * @default "CANCEL"
+ */
+ cancelText: PropTypes.node,
+ /**
+ * className applied to the root component.
+ */
+ className: PropTypes.string,
+ /**
+ * If `true`, it shows the clear action in the picker dialog.
+ * @default false
+ */
+ clearable: PropTypes.bool,
+ /**
+ * "CLEAR" Text message
+ * @default "CLEAR"
+ */
+ clearText: PropTypes.node,
+ /**
+ * Allows to pass configured date-io adapter directly. More info [here](https://next.material-ui-pickers.dev/guides/date-adapter-passing)
+ * ```jsx
+ * dateAdapter={new DateFnsAdapter({ locale: ruLocale })}
+ * ```
+ */
+ dateAdapter: PropTypes.object,
+ /**
+ * Props to be passed directly to material-ui [Dialog](https://material-ui.com/components/dialogs)
+ */
+ DialogProps: PropTypes.object,
+ /**
+ * If `true` the popup or dialog will immediately close after submitting full date.
+ * @default `true` for Desktop, `false` for Mobile (based on the chosen wrapper and `desktopModeMediaQuery` prop).
+ */
+ disableCloseOnSelect: PropTypes.bool,
+ /**
+ * If `true`, the picker and text field are disabled.
+ */
+ disabled: PropTypes.bool,
+ /**
+ * Disable mask on the keyboard, this should be used rarely. Consider passing proper mask for your format.
+ * @default false
+ */
+ disableMaskedInput: PropTypes.bool,
+ /**
+ * Do not render open picker button (renders only text field with validation).
+ * @default false
+ */
+ disableOpenPicker: PropTypes.bool,
+ /**
+ * Get aria-label text for control that opens picker dialog. Aria-label text must include selected date. @DateIOType
+ * @default (value, utils) => `Choose date, selected date is ${utils.format(utils.date(value), 'fullDate')}`
+ */
+ getOpenDialogAriaText: PropTypes.func,
+ /**
+ * @ignore
+ */
+ ignoreInvalidInputs: PropTypes.bool,
+ /**
+ * Props to pass to keyboard input adornment.
+ */
+ InputAdornmentProps: PropTypes.object,
+ /**
+ * Format string.
+ */
+ inputFormat: PropTypes.string,
+ /**
+ * @ignore
+ */
+ InputProps: PropTypes.object,
+ /**
+ * @ignore
+ */
+ key: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+ /**
+ * @ignore
+ */
+ label: PropTypes.node,
+ /**
+ * Custom mask. Can be used to override generate from format. (e.g. __/__/____ __:__ or __/__/____ __:__ _M)
+ */
+ mask: PropTypes.string,
+ /**
+ * @ignore
+ */
+ maxDate: PropTypes.oneOfType([
+ PropTypes.any,
+ PropTypes.instanceOf(Date),
+ PropTypes.number,
+ PropTypes.string,
+ ]),
+ /**
+ * @ignore
+ */
+ minDate: PropTypes.oneOfType([
+ PropTypes.any,
+ PropTypes.instanceOf(Date),
+ PropTypes.number,
+ PropTypes.string,
+ ]),
+ /**
+ * "OK" button text.
+ * @default "OK"
+ */
+ okText: PropTypes.node,
+ /**
+ * Callback fired when date is accepted @DateIOType.
+ */
+ onAccept: PropTypes.func,
+ /**
+ * Callback fired when the value (the selected date) changes. @DateIOType.
+ */
+ onChange: PropTypes.func.isRequired,
+ /**
+ * Callback fired when the popup requests to be closed.
+ * Use in controlled mode (see open).
+ */
+ onClose: PropTypes.func,
+ /**
+ * Callback that fired when input value or new `value` prop validation returns **new** validation error (or value is valid after error).
+ * In case of validation error detected `reason` prop return non-null value and `TextField` must be displayed in `error` state.
+ * This can be used to render appropriate form error.
+ *
+ * [Read the guide](https://next.material-ui-pickers.dev/guides/forms) about form integration and error displaying.
+ * @DateIOType
+ */
+ onError: PropTypes.func,
+ /**
+ * Callback fired when the popup requests to be opened.
+ * Use in controlled mode (see open).
+ */
+ onOpen: PropTypes.func,
+ /**
+ * Control the popup or dialog open state.
+ */
+ open: PropTypes.bool,
+ /**
+ * Props to pass to keyboard adornment button.
+ */
+ OpenPickerButtonProps: PropTypes.object,
+ /**
+ * Icon displaying for open picker button.
+ */
+ openPickerIcon: PropTypes.node,
+ /**
+ * Force rendering in particular orientation.
+ */
+ orientation: PropTypes.oneOf(['landscape', 'portrait']),
+ /**
+ * Make picker read only.
+ */
+ readOnly: PropTypes.bool,
+ /**
+ * The `renderInput` prop allows you to customize the rendered input.
+ * The `props` argument of this render prop contains props of [TextField](https://material-ui.com/api/text-field/#textfield-api) that you need to forward.
+ * Pay specific attention to the `ref` and `inputProps` keys.
+ * @example ```jsx
+ * renderInput={props => }
+ * ````
+ */
+ renderInput: PropTypes.func.isRequired,
+ /**
+ * Custom formatter to be passed into Rifm component.
+ */
+ rifmFormatter: PropTypes.func,
+ /**
+ * If `true`, the today button will be displayed. **Note** that `showClearButton` has a higher priority.
+ * @default false
+ */
+ showTodayButton: PropTypes.bool,
+ /**
+ * If `true`, show the toolbar even in desktop mode.
+ */
+ showToolbar: PropTypes.bool,
+ /**
+ * "TODAY" Text message
+ * @default "TODAY"
+ */
+ todayText: PropTypes.node,
+ /**
+ * Component that will replace default toolbar renderer.
+ */
+ ToolbarComponent: PropTypes.elementType,
+ /**
+ * Date format, that is displaying in toolbar.
+ */
+ toolbarFormat: PropTypes.string,
+ /**
+ * Mobile picker date value placeholder, displaying if `value` === `null`.
+ * @default "–"
+ */
+ toolbarPlaceholder: PropTypes.node,
+ /**
+ * Mobile picker title, displaying in the toolbar.
+ * @default "SELECT DATE"
+ */
+ toolbarTitle: PropTypes.node,
+ /**
+ * The value of the picker.
+ */
+ value: PropTypes.oneOfType([
+ PropTypes.any,
+ PropTypes.instanceOf(Date),
+ PropTypes.number,
+ PropTypes.string,
+ ]),
+};
+
+export type DatePickerProps = React.ComponentProps;
+
+export default DatePicker;
diff --git a/packages/material-ui-lab/src/DatePicker/DatePickerKeyboard.test.tsx b/packages/material-ui-lab/src/DatePicker/DatePickerKeyboard.test.tsx
new file mode 100644
index 00000000000000..1a0c71273e0b78
--- /dev/null
+++ b/packages/material-ui-lab/src/DatePicker/DatePickerKeyboard.test.tsx
@@ -0,0 +1,275 @@
+import * as React from 'react';
+import { expect } from 'chai';
+import { spy } from 'sinon';
+import { isWeekend } from 'date-fns';
+import TextField from '@material-ui/core/TextField';
+import { fireEvent, screen, act } from 'test/utils';
+import DesktopDatePicker, { DesktopDatePickerProps } from '@material-ui/lab/DesktopDatePicker';
+import StaticDatePicker from '@material-ui/lab/StaticDatePicker';
+import { createPickerRender } from '../internal/pickers/test-utils';
+import { MakeOptional } from '../internal/pickers/typings/helpers';
+
+function TestKeyboardDatePicker(
+ PickerProps: MakeOptional,
+) {
+ const [value, setValue] = React.useState(
+ PickerProps.value ?? new Date('2019-01-01T00:00:00.000'),
+ );
+
+ return (
+ setValue(newDate)}
+ renderInput={(props) => }
+ {...PickerProps}
+ />
+ );
+}
+
+describe(' keyboard interactions', () => {
+ const render = createPickerRender({ strict: false });
+
+ context('input', () => {
+ it('allows to change selected date from the input according to `format`', () => {
+ const onChangeMock = spy();
+ render( );
+
+ fireEvent.change(screen.getByRole('textbox'), {
+ target: { value: '10/11/2018' },
+ });
+
+ expect(screen.getByRole('textbox')).to.have.value('10/11/2018');
+ expect(onChangeMock.callCount).to.equal(1);
+ });
+
+ it("doesn't accept invalid date format", () => {
+ render( );
+
+ fireEvent.change(screen.getByRole('textbox'), {
+ target: { value: '01' },
+ });
+ expect(screen.getByRole('textbox')).to.have.attr('aria-invalid', 'true');
+ });
+
+ it('removes invalid state when chars are cleared from invalid input', () => {
+ render( );
+
+ fireEvent.change(screen.getByRole('textbox'), {
+ target: { value: '01' },
+ });
+ expect(screen.getByRole('textbox')).to.have.attr('aria-invalid', 'true');
+ fireEvent.change(screen.getByRole('textbox'), {
+ target: { value: '' },
+ });
+ expect(screen.getByRole('textbox')).to.have.attr('aria-invalid', 'false');
+ });
+
+ it('renders correct format helper text and placeholder', () => {
+ render(
+ }
+ />,
+ );
+
+ const helperText = document.getElementById('test-helper-text');
+ expect(helperText).to.have.text('yyyy');
+
+ expect(screen.getByRole('textbox')).to.have.attr('placeholder', 'yyyy');
+ });
+
+ it('correctly input dates according to the input mask', () => {
+ render( );
+ const input = screen.getByRole('textbox');
+
+ fireEvent.change(screen.getByRole('textbox'), {
+ target: { value: '011' },
+ });
+ expect(input).to.have.value('01/1');
+
+ fireEvent.change(screen.getByRole('textbox'), {
+ target: { value: '01102019' },
+ });
+ expect(input).to.have.value('01/10/2019');
+ });
+
+ it('prop `disableMaskedInput` – disables mask and allows to input anything to the field', () => {
+ render( );
+
+ fireEvent.change(screen.getByRole('textbox'), {
+ target: { value: 'any text' },
+ });
+
+ const input = screen.getByRole('textbox');
+ expect(input).to.have.value('any text');
+ expect(input).to.have.attr('aria-invalid', 'true');
+ });
+
+ it('prop `disableMaskedInput` – correctly parses date string when mask is disabled', () => {
+ const onChangeMock = spy();
+ render( );
+
+ fireEvent.change(screen.getByRole('textbox'), {
+ target: { value: '01/10/2019' },
+ });
+
+ const input = screen.getByRole('textbox');
+ expect(input).to.have.value('01/10/2019');
+ expect(input).to.have.attribute('aria-invalid', 'false');
+ expect(onChangeMock.callCount).to.equal(1);
+ });
+ });
+
+ context('Calendar keyboard navigation', () => {
+ beforeEach(() => {
+ // Important: Use here in order to avoid async waiting for focus because of packages/material-ui-lab/src/internal/pickers/hooks/useCanAutoFocus.tsx logic
+ render(
+ {}}
+ renderInput={(params) => }
+ />,
+ );
+ });
+
+ // TODO
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip('autofocus selected day on mount', () => {
+ expect(screen.getByLabelText('Aug 13, 2020')).toHaveFocus();
+ });
+
+ [
+ { keyCode: 35, key: 'End', expectFocusedDay: 'Aug 15, 2020' },
+ { keyCode: 36, key: 'Home', expectFocusedDay: 'Aug 9, 2020' },
+ { keyCode: 37, key: 'ArrowLeft', expectFocusedDay: 'Aug 12, 2020' },
+ { keyCode: 38, key: 'ArrowUp', expectFocusedDay: 'Aug 6, 2020' },
+ { keyCode: 39, key: 'ArrowRight', expectFocusedDay: 'Aug 14, 2020' },
+ { keyCode: 40, key: 'ArrowDown', expectFocusedDay: 'Aug 20, 2020' },
+ ].forEach(({ key, keyCode, expectFocusedDay }) => {
+ it(key, () => {
+ fireEvent.keyDown(document.body, { force: true, keyCode, key });
+
+ expect(document.activeElement).toHaveAccessibleName(expectFocusedDay);
+ });
+ });
+ });
+
+ // TODO
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip("doesn't allow to select disabled date from keyboard", async () => {
+ render(
+ {}}
+ renderInput={(params) => }
+ />,
+ );
+
+ expect(document.activeElement).to.have.attr('aria-label', 'Aug 13, 2020');
+
+ // eslint-disable-next-line no-plusplus
+ for (let i = 0; i < 3; i++) {
+ fireEvent.keyDown(document.body, { force: true, keyCode: 37, key: 'ArrowLeft' });
+ }
+
+ // leaves focus on the same date
+ expect(document.activeElement).to.have.attr('aria-label', 'Aug 13, 2020');
+ });
+
+ context('YearPicker keyboard navigation', () => {
+ [
+ { keyCode: 37, key: 'ArrowLeft', expectFocusedYear: '2019' },
+ { keyCode: 38, key: 'ArrowUp', expectFocusedYear: '2016' },
+ { keyCode: 39, key: 'ArrowRight', expectFocusedYear: '2021' },
+ { keyCode: 40, key: 'ArrowDown', expectFocusedYear: '2024' },
+ ].forEach(({ key, keyCode, expectFocusedYear }) => {
+ // TODO
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip(key, () => {
+ render(
+ {}}
+ renderInput={(params) => }
+ />,
+ );
+
+ fireEvent.keyDown(document.body, { force: true, keyCode, key });
+
+ expect(document.activeElement).to.have.text(expectFocusedYear);
+ });
+ });
+ });
+
+ context('input validaiton', () => {
+ [
+ { expectedError: 'invalidDate', props: {}, input: 'invalidText' },
+ { expectedError: 'disablePast', props: { disablePast: true }, input: '01/01/1900' },
+ { expectedError: 'disableFuture', props: { disableFuture: true }, input: '01/01/2050' },
+ { expectedError: 'minDate', props: { minDate: new Date('01/01/2000') }, input: '01/01/1990' },
+ { expectedError: 'maxDate', props: { maxDate: new Date('01/01/2000') }, input: '01/01/2010' },
+ {
+ expectedError: 'shouldDisableDate',
+ props: { shouldDisableDate: isWeekend },
+ input: '04/25/2020',
+ },
+ ].forEach(({ props, input, expectedError }) => {
+ it(`dispatches ${expectedError} error`, () => {
+ const onErrorMock = spy();
+ // we are running validation on value change
+ function DatePickerInput() {
+ const [date, setDate] = React.useState(null);
+
+ return (
+
+ value={date}
+ onError={onErrorMock}
+ onChange={(newDate) => setDate(newDate)}
+ renderInput={(inputProps) => }
+ {...props}
+ />
+ );
+ }
+
+ render( );
+
+ fireEvent.change(screen.getByRole('textbox'), {
+ target: {
+ value: input,
+ },
+ });
+
+ expect(onErrorMock.calledWith(expectedError)).to.be.equal(true);
+ });
+ });
+ });
+
+ // TODO
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip('Opens calendar by keydown on the open button', () => {
+ render( );
+ const openButton = screen.getByLabelText(/choose date/i);
+
+ act(() => {
+ openButton.focus();
+ });
+
+ fireEvent.keyDown(openButton, {
+ key: 'Enter',
+ keyCode: 13,
+ });
+
+ expect(screen.queryByRole('dialog')).toBeVisible();
+ });
+});
diff --git a/packages/material-ui-lab/src/DatePicker/DatePickerLocalization.test.tsx b/packages/material-ui-lab/src/DatePicker/DatePickerLocalization.test.tsx
new file mode 100644
index 00000000000000..d25b9ee719d347
--- /dev/null
+++ b/packages/material-ui-lab/src/DatePicker/DatePickerLocalization.test.tsx
@@ -0,0 +1,127 @@
+import * as React from 'react';
+import { expect } from 'chai';
+import fr from 'date-fns/locale/fr';
+import deLocale from 'date-fns/locale/de';
+import enLocale from 'date-fns/locale/en-US';
+import TextField from '@material-ui/core/TextField';
+import MobileDatePicker from '@material-ui/lab/MobileDatePicker';
+import DesktopDatePicker, { DesktopDatePickerProps } from '@material-ui/lab/DesktopDatePicker';
+import { fireEvent, screen } from 'test/utils';
+import { adapterToUse, getByMuiTest, createPickerRender } from '../internal/pickers/test-utils';
+
+describe(' localization', () => {
+ const render = createPickerRender({ strict: false, locale: fr });
+
+ // TODO
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip('datePicker localized format for year view', () => {
+ render(
+ }
+ value={adapterToUse.date('2018-01-01T00:00:00.000')}
+ onChange={() => {}}
+ views={['year']}
+ />,
+ );
+
+ expect(screen.getByRole('textbox')).to.have.value('2018');
+
+ fireEvent.click(screen.getByLabelText(/Choose date/));
+ expect(getByMuiTest('datepicker-toolbar-date').textContent).to.equal('2018');
+ });
+
+ it('datePicker localized format for year+month view', () => {
+ render(
+ }
+ value={adapterToUse.date('2018-01-01T00:00:00.000')}
+ onChange={() => {}}
+ views={['year', 'month']}
+ />,
+ );
+
+ expect(screen.getByRole('textbox')).to.have.value('janvier 2018');
+
+ fireEvent.click(screen.getByLabelText(/Choose date/));
+ expect(getByMuiTest('datepicker-toolbar-date').textContent).to.equal('janvier');
+ });
+
+ it('datePicker localized format for year+month+date view', () => {
+ render(
+ {}}
+ renderInput={(params) => }
+ value={adapterToUse.date('2018-01-01T00:00:00.000')}
+ views={['year', 'month', 'date']}
+ />,
+ );
+
+ expect(screen.getByRole('textbox')).to.have.value('01/01/2018');
+
+ fireEvent.click(screen.getByLabelText(/Choose date/));
+ expect(getByMuiTest('datepicker-toolbar-date').textContent).to.equal('1 janvier');
+ });
+
+ describe('input validation', () => {
+ interface FormProps {
+ Picker: React.ElementType;
+ PickerProps: Partial;
+ }
+
+ const Form = (props: FormProps) => {
+ const { Picker, PickerProps } = props;
+ const [value, setValue] = React.useState(new Date('01/01/2020'));
+
+ return (
+ }
+ value={value}
+ {...PickerProps}
+ />
+ );
+ };
+
+ const tests = [
+ {
+ locale: 'en-US',
+ valid: 'January 2020',
+ invalid: 'Januar 2020',
+ dateFnsLocale: enLocale,
+ },
+ {
+ locale: 'de',
+ valid: 'Januar 2020',
+ invalid: 'Janua 2020',
+ dateFnsLocale: deLocale,
+ },
+ ];
+
+ tests.forEach(({ valid, invalid, locale, dateFnsLocale }) => {
+ const localizedRender = createPickerRender({ strict: false, locale: dateFnsLocale });
+
+ it(`${locale}: should set invalid`, () => {
+ localizedRender(
+ ,
+ );
+
+ const input = screen.getByRole('textbox');
+ fireEvent.change(input, { target: { value: invalid } });
+
+ expect(input).to.have.attribute('aria-invalid', 'true');
+ });
+
+ it(`${locale}: should set to valid when was invalid`, () => {
+ localizedRender(
+ ,
+ );
+
+ const input = screen.getByRole('textbox');
+ fireEvent.change(input, { target: { value: invalid } });
+ fireEvent.change(input, { target: { value: valid } });
+
+ expect(input).to.have.attribute('aria-invalid', 'false');
+ });
+ });
+ });
+});
diff --git a/packages/material-ui-lab/src/DatePicker/DatePickerToolbar.tsx b/packages/material-ui-lab/src/DatePicker/DatePickerToolbar.tsx
new file mode 100644
index 00000000000000..24e1e107f9a960
--- /dev/null
+++ b/packages/material-ui-lab/src/DatePicker/DatePickerToolbar.tsx
@@ -0,0 +1,88 @@
+import * as React from 'react';
+import clsx from 'clsx';
+import Typography from '@material-ui/core/Typography';
+import { createStyles, WithStyles, withStyles } from '@material-ui/core/styles';
+import PickerToolbar from '../internal/pickers/PickersToolbar';
+import { useUtils } from '../internal/pickers/hooks/useUtils';
+import { isYearAndMonthViews, isYearOnlyView } from '../internal/pickers/date-utils';
+import { DatePickerView } from '../internal/pickers/typings/Views';
+import { ToolbarComponentProps } from '../internal/pickers/typings/BasePicker';
+
+export const styles = createStyles({
+ root: {},
+ dateTitleLandscape: {
+ margin: 'auto 16px auto auto',
+ },
+ penIcon: {
+ position: 'relative',
+ top: 4,
+ },
+});
+export type DatePickerToolbarClassKey = keyof WithStyles['classes'];
+
+/**
+ * @ignore - internal component.
+ */
+const DatePickerToolbar: React.FC> = ({
+ classes,
+ date,
+ isLandscape,
+ isMobileKeyboardViewOpen,
+ onChange,
+ toggleMobileKeyboardView,
+ toolbarFormat,
+ toolbarPlaceholder = '––',
+ toolbarTitle = 'SELECT DATE',
+ views,
+ ...other
+}) => {
+ const utils = useUtils();
+
+ const dateText = React.useMemo(() => {
+ if (!date) {
+ return toolbarPlaceholder;
+ }
+
+ if (toolbarFormat) {
+ return utils.formatByString(date, toolbarFormat);
+ }
+
+ if (isYearOnlyView(views as DatePickerView[])) {
+ return utils.format(date, 'year');
+ }
+
+ if (isYearAndMonthViews(views as DatePickerView[])) {
+ return utils.format(date, 'month');
+ }
+
+ // Little localization hack (Google is doing the same for android native pickers):
+ // For english localization it is convenient to include weekday into the date "Mon, Jun 1"
+ // For other locales using strings like "June 1", without weekday
+ return /en/.test(utils.getCurrentLocaleCode())
+ ? utils.format(date, 'normalDateWithWeekday')
+ : utils.format(date, 'normalDate');
+ }, [date, toolbarFormat, toolbarPlaceholder, utils, views]);
+
+ return (
+
+
+ {dateText}
+
+
+ );
+};
+
+export default withStyles(styles, { name: 'MuiDatePickerToolbar' })(DatePickerToolbar);
diff --git a/packages/material-ui-lab/src/DatePicker/index.ts b/packages/material-ui-lab/src/DatePicker/index.ts
new file mode 100644
index 00000000000000..a4c99812fd6629
--- /dev/null
+++ b/packages/material-ui-lab/src/DatePicker/index.ts
@@ -0,0 +1,2 @@
+export { default } from './DatePicker';
+export * from './DatePicker';
diff --git a/packages/pickers/lib/src/DateRangePicker/DateRangeDelimiter.tsx b/packages/material-ui-lab/src/DateRangeDelimiter/DateRangeDelimiter.tsx
similarity index 63%
rename from packages/pickers/lib/src/DateRangePicker/DateRangeDelimiter.tsx
rename to packages/material-ui-lab/src/DateRangeDelimiter/DateRangeDelimiter.tsx
index cc0751e5ba96d5..cd3956b497b55c 100644
--- a/packages/pickers/lib/src/DateRangePicker/DateRangeDelimiter.tsx
+++ b/packages/material-ui-lab/src/DateRangeDelimiter/DateRangeDelimiter.tsx
@@ -1,12 +1,17 @@
import Typography from '@material-ui/core/Typography';
import { styled } from '@material-ui/core/styles';
-import { withDefaultProps } from '../_shared/withDefaultProps';
-export const DateRangeDelimiter = withDefaultProps(
- { name: 'MuiPickersDateRangeDelimiter' },
- styled(Typography)({
+const DateRangeDelimiter = styled(Typography)(
+ {
margin: '0 16px',
- })
+ },
+ { name: 'MuiPickersDateRangeDelimiter' },
);
export type DateRangeDelimiterProps = React.ComponentProps;
+
+/**
+ * TODO use Box
+ * @ignore - internal component.
+ */
+export default DateRangeDelimiter;
diff --git a/packages/material-ui-lab/src/DateRangeDelimiter/index.ts b/packages/material-ui-lab/src/DateRangeDelimiter/index.ts
new file mode 100644
index 00000000000000..0b8fd34b98def9
--- /dev/null
+++ b/packages/material-ui-lab/src/DateRangeDelimiter/index.ts
@@ -0,0 +1,2 @@
+export * from './DateRangeDelimiter';
+export { default } from './DateRangeDelimiter';
diff --git a/packages/material-ui-lab/src/DateRangePicker/DateRangePicker.test.tsx b/packages/material-ui-lab/src/DateRangePicker/DateRangePicker.test.tsx
new file mode 100644
index 00000000000000..3ed98b98f266b7
--- /dev/null
+++ b/packages/material-ui-lab/src/DateRangePicker/DateRangePicker.test.tsx
@@ -0,0 +1,289 @@
+import * as React from 'react';
+import { expect } from 'chai';
+import { spy } from 'sinon';
+import { isWeekend } from 'date-fns';
+import { screen, fireEvent } from 'test/utils';
+import TextField, { TextFieldProps } from '@material-ui/core/TextField';
+import DesktopDateRangePicker, { DateRange } from '@material-ui/lab/DesktopDateRangePicker';
+import StaticDateRangePicker from '@material-ui/lab/StaticDateRangePicker';
+import {
+ createPickerRender,
+ FakeTransitionComponent,
+ adapterToUse,
+ getAllByMuiTest,
+ queryByMuiTest,
+} from '../internal/pickers/test-utils';
+
+const defaultRangeRenderInput = (startProps: TextFieldProps, endProps: TextFieldProps) => (
+
+
+
+
+);
+
+describe(' ', () => {
+ const render = createPickerRender({ strict: false });
+
+ before(function beforeHook() {
+ if (!/jsdom/.test(window.navigator.userAgent)) {
+ // FIXME This test suite is extremely flaky in test:karma
+ this.skip();
+ }
+ });
+
+ it('allows to select date range end-to-end', () => {
+ function RangePickerTest() {
+ const [range, changeRange] = React.useState>([
+ new Date('2019-01-01T00:00:00.000'),
+ new Date('2019-01-01T00:00:00.000'),
+ ]);
+
+ return (
+ changeRange(date)}
+ value={range}
+ TransitionComponent={FakeTransitionComponent}
+ />
+ );
+ }
+
+ render( );
+
+ fireEvent.click(screen.getByLabelText('Jan 1, 2019'));
+ fireEvent.click(screen.getByLabelText('Jan 24, 2019'));
+
+ expect(getAllByMuiTest('DateRangeHighlight')).to.have.length(24);
+ });
+
+ it('highlights the selected range of dates', () => {
+ render(
+ {}}
+ value={[
+ adapterToUse.date(new Date('2018-01-01T00:00:00.000Z')),
+ adapterToUse.date(new Date('2018-01-31T00:00:00.000Z')),
+ ]}
+ />,
+ );
+
+ expect(getAllByMuiTest('DateRangeHighlight')).to.have.length(31);
+ });
+
+ it('selects the range from the next month', () => {
+ const onChangeMock = spy();
+ render(
+ ,
+ );
+
+ fireEvent.click(screen.getByLabelText('Jan 1, 2019'));
+ fireEvent.click(
+ screen.getByLabelText('next month', { selector: ':not([aria-hidden="true"])' }),
+ );
+ fireEvent.click(screen.getByLabelText('Mar 19, 2019'));
+
+ expect(onChangeMock.callCount).to.equal(2);
+ const [changedRange] = onChangeMock.lastCall.args;
+ expect(changedRange[0]).to.toEqualDateTime(new Date('2019-01-01T00:00:00.000'));
+ expect(changedRange[1]).to.toEqualDateTime(new Date('2019-03-19T00:00:00.000'));
+ });
+
+ it('continues start selection if selected "end" date is before start', () => {
+ const onChangeMock = spy();
+ render(
+ ,
+ );
+
+ fireEvent.click(screen.getByLabelText('Jan 30, 2019'));
+ fireEvent.click(screen.getByLabelText('Jan 19, 2019'));
+
+ expect(queryByMuiTest(document.body, 'DateRangeHighlight')).to.equal(null);
+
+ fireEvent.click(screen.getByLabelText('Jan 30, 2019'));
+
+ expect(onChangeMock.callCount).to.equal(3);
+ const [changedRange] = onChangeMock.lastCall.args;
+ expect(changedRange[0]).to.toEqualDateTime(new Date('2019-01-19T00:00:00.000'));
+ expect(changedRange[1]).to.toEqualDateTime(new Date('2019-01-30T00:00:00.000'));
+ });
+
+ it('starts selection from end if end text field was focused', function test() {
+ const onChangeMock = spy();
+ render(
+ ,
+ );
+
+ fireEvent.focus(screen.getAllByRole('textbox')[1]);
+
+ fireEvent.click(screen.getByLabelText('Jan 30, 2019'));
+ fireEvent.click(screen.getByLabelText('Jan 19, 2019'));
+
+ expect(getAllByMuiTest('DateRangeHighlight')).to.have.length(12);
+ expect(onChangeMock.callCount).to.equal(2);
+ const [changedRange] = onChangeMock.lastCall.args;
+ expect(changedRange[0]).toEqualDateTime(new Date('2019-01-19T00:00:00.000'));
+ expect(changedRange[1]).toEqualDateTime(new Date('2019-01-30T00:00:00.000'));
+ });
+
+ it('closes on focus out of fields', () => {
+ render(
+
+ {}}
+ TransitionComponent={FakeTransitionComponent}
+ />
+ focus me
+ ,
+ );
+
+ fireEvent.focus(screen.getAllByRole('textbox')[0]);
+ expect(screen.getByRole('tooltip')).toBeVisible();
+
+ fireEvent.focus(screen.getByText('focus me'));
+ expect(screen.getByRole('tooltip')).not.toBeVisible();
+ });
+
+ // TODO
+ // eslint-disable-next-line mocha/no-skipped-tests
+ it.skip('allows pure keyboard selection of range', () => {
+ const onChangeMock = spy();
+ render(
+ ,
+ );
+
+ fireEvent.focus(screen.getAllByRole('textbox')[0]);
+ fireEvent.change(screen.getAllByRole('textbox')[0], {
+ target: {
+ value: '06/06/2019',
+ },
+ });
+
+ fireEvent.change(screen.getAllByRole('textbox')[1], {
+ target: {
+ value: '08/08/2019',
+ },
+ });
+
+ expect(
+ onChangeMock.calledWith([
+ new Date('2019-06-06T00:00:00.000'),
+ new Date('2019-06-06T00:00:00.000'),
+ ]),
+ ).to.equal(true);
+ });
+
+ it('scrolls current month to the active selection on focusing appropriate field', () => {
+ render(
+ {}}
+ TransitionComponent={FakeTransitionComponent}
+ />,
+ );
+
+ fireEvent.focus(screen.getAllByRole('textbox')[0]);
+ expect(screen.getByText('May 2019')).toBeVisible();
+
+ fireEvent.focus(screen.getAllByRole('textbox')[1]);
+ expect(screen.getByText('October 2019')).toBeVisible();
+
+ // scroll back
+ fireEvent.focus(screen.getAllByRole('textbox')[0]);
+ expect(screen.getByText('May 2019')).toBeVisible();
+ });
+
+ it('allows disabling dates', () => {
+ render(
+ {}}
+ value={[
+ adapterToUse.date('2018-01-01T00:00:00.000'),
+ adapterToUse.date('2018-01-31T00:00:00.000'),
+ ]}
+ />,
+ );
+
+ expect(
+ getAllByMuiTest('DateRangeDay').filter((day) => day.getAttribute('disabled') !== undefined),
+ ).to.have.length(31);
+ });
+
+ it(`doesn't crash if opening picker with invalid date input`, async () => {
+ render(
+ {}}
+ TransitionComponent={FakeTransitionComponent}
+ value={[adapterToUse.date(new Date(NaN)), adapterToUse.date('2018-01-31T00:00:00.000')]}
+ />,
+ );
+
+ fireEvent.focus(screen.getAllByRole('textbox')[0]);
+ expect(screen.getByRole('tooltip')).toBeVisible();
+ });
+
+ it('prop – `renderDay` should be called and render days', async () => {
+ render(
+ {}}
+ renderDay={(day) =>
}
+ value={[null, null]}
+ />,
+ );
+
+ expect(getAllByMuiTest('renderDayCalled')).not.to.have.length(0);
+ });
+
+ it('prop – `calendars` renders provided amount of calendars', () => {
+ render(
+ {}}
+ value={[
+ adapterToUse.date('2018-01-01T00:00:00.000'),
+ adapterToUse.date('2018-01-31T00:00:00.000'),
+ ]}
+ />,
+ );
+
+ expect(getAllByMuiTest('pickers-calendar')).to.have.length(3);
+ });
+});
diff --git a/packages/material-ui-lab/src/DateRangePicker/DateRangePicker.tsx b/packages/material-ui-lab/src/DateRangePicker/DateRangePicker.tsx
new file mode 100644
index 00000000000000..394bcf506b9550
--- /dev/null
+++ b/packages/material-ui-lab/src/DateRangePicker/DateRangePicker.tsx
@@ -0,0 +1,372 @@
+import PropTypes from 'prop-types';
+import { ResponsiveTooltipWrapper } from '../internal/pickers/wrappers/ResponsiveWrapper';
+import { makeDateRangePicker } from './makeDateRangePicker';
+
+/**
+ * @ignore - do not document.
+ */
+/* @GeneratePropTypes */
+const DateRangePicker = makeDateRangePicker('MuiPickersDateRangePicker', ResponsiveTooltipWrapper);
+
+(DateRangePicker as any).propTypes = {
+ // ----------------------------- Warning --------------------------------
+ // | These PropTypes are generated from the TypeScript type definitions |
+ // | To update them edit TypeScript types and run "yarn proptypes" |
+ // ----------------------------------------------------------------------
+ /**
+ * Regular expression to detect "accepted" symbols.
+ * @default /\dap/gi
+ */
+ acceptRegex: PropTypes.instanceOf(RegExp),
+ /**
+ * Enables keyboard listener for moving between days in calendar.
+ * @default currentWrapper !== 'static'
+ */
+ allowKeyboardControl: PropTypes.bool,
+ /**
+ * If `true`, `onChange` is fired on click even if the same date is selected.
+ * @default false
+ */
+ allowSameDateSelection: PropTypes.bool,
+ /**
+ * The number of calendars that render on **desktop**.
+ * @default 2
+ */
+ calendars: PropTypes.oneOf([1, 2, 3]),
+ /**
+ * "CANCEL" Text message
+ * @default "CANCEL"
+ */
+ cancelText: PropTypes.node,
+ /**
+ * className applied to the root component.
+ */
+ className: PropTypes.string,
+ /**
+ * If `true`, it shows the clear action in the picker dialog.
+ * @default false
+ */
+ clearable: PropTypes.bool,
+ /**
+ * "CLEAR" Text message
+ * @default "CLEAR"
+ */
+ clearText: PropTypes.node,
+ /**
+ * Allows to pass configured date-io adapter directly. More info [here](https://next.material-ui-pickers.dev/guides/date-adapter-passing)
+ * ```jsx
+ * dateAdapter={new DateFnsAdapter({ locale: ruLocale })}
+ * ```
+ */
+ dateAdapter: PropTypes.object,
+ /**
+ * Default calendar month displayed when `value={null}`.
+ * @default `new Date()`
+ */
+ defaultCalendarMonth: PropTypes.any,
+ /**
+ * CSS media query when `Mobile` mode will be changed to `Desktop`.
+ * @default "@media (pointer: fine)"
+ * @example "@media (min-width: 720px)" or theme.breakpoints.up("sm")
+ */
+ desktopModeMediaQuery: PropTypes.string,
+ /**
+ * Props to be passed directly to material-ui [Dialog](https://material-ui.com/components/dialogs)
+ */
+ DialogProps: PropTypes.object,
+ /**
+ * if `true` after selecting `start` date calendar will not automatically switch to the month of `end` date
+ * @default false
+ */
+ disableAutoMonthSwitching: PropTypes.bool,
+ /**
+ * If `true` the popup or dialog will immediately close after submitting full date.
+ * @default `true` for Desktop, `false` for Mobile (based on the chosen wrapper and `desktopModeMediaQuery` prop).
+ */
+ disableCloseOnSelect: PropTypes.bool,
+ /**
+ * If `true`, the picker and text field are disabled.
+ */
+ disabled: PropTypes.bool,
+ /**
+ * Disable future dates.
+ * @default false
+ */
+ disableFuture: PropTypes.bool,
+ /**
+ * If `true`, todays date is rendering without highlighting with circle.
+ * @default false
+ */
+ disableHighlightToday: PropTypes.bool,
+ /**
+ * Disable mask on the keyboard, this should be used rarely. Consider passing proper mask for your format.
+ * @default false
+ */
+ disableMaskedInput: PropTypes.bool,
+ /**
+ * Do not render open picker button (renders only text field with validation).
+ * @default false
+ */
+ disableOpenPicker: PropTypes.bool,
+ /**
+ * Disable past dates.
+ * @default false
+ */
+ disablePast: PropTypes.bool,
+ /**
+ * Text for end input label and toolbar placeholder.
+ * @default "end"
+ */
+ endText: PropTypes.node,
+ /**
+ * Get aria-label text for control that opens picker dialog. Aria-label text must include selected date. @DateIOType
+ * @default (value, utils) => `Choose date, selected date is ${utils.format(utils.date(value), 'fullDate')}`
+ */
+ getOpenDialogAriaText: PropTypes.func,
+ /**
+ * Get aria-label text for switching between views button.
+ */
+ getViewSwitchingButtonText: PropTypes.func,
+ /**
+ * @ignore
+ */
+ ignoreInvalidInputs: PropTypes.bool,
+ /**
+ * Props to pass to keyboard input adornment.
+ */
+ InputAdornmentProps: PropTypes.object,
+ /**
+ * Format string.
+ */
+ inputFormat: PropTypes.string,
+ /**
+ * @ignore
+ */
+ InputProps: PropTypes.object,
+ /**
+ * @ignore
+ */
+ key: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+ /**
+ * @ignore
+ */
+ label: PropTypes.node,
+ /**
+ * Props to pass to left arrow button.
+ */
+ leftArrowButtonProps: PropTypes.object,
+ /**
+ * Left arrow icon aria-label text.
+ */
+ leftArrowButtonText: PropTypes.string,
+ /**
+ * Left arrow icon.
+ */
+ leftArrowIcon: PropTypes.node,
+ /**
+ * If `true` renders `LoadingComponent` in calendar instead of calendar view.
+ * Can be used to preload information and show it in calendar.
+ * @default false
+ */
+ loading: PropTypes.bool,
+ /**
+ * Custom mask. Can be used to override generate from format. (e.g. __/__/____ __:__ or __/__/____ __:__ _M)
+ */
+ mask: PropTypes.string,
+ /**
+ * Max selectable date. @DateIOType
+ * @default Date(2099-31-12)
+ */
+ maxDate: PropTypes.any,
+ /**
+ * Min selectable date. @DateIOType
+ * @default Date(1900-01-01)
+ */
+ minDate: PropTypes.any,
+ /**
+ * "OK" button text.
+ * @default "OK"
+ */
+ okText: PropTypes.node,
+ /**
+ * Callback fired when date is accepted @DateIOType.
+ */
+ onAccept: PropTypes.func,
+ /**
+ * Callback fired when the value (the selected date) changes. @DateIOType.
+ */
+ onChange: PropTypes.func.isRequired,
+ /**
+ * Callback fired when the popup requests to be closed.
+ * Use in controlled mode (see open).
+ */
+ onClose: PropTypes.func,
+ /**
+ * Callback that fired when input value or new `value` prop validation returns **new** validation error (or value is valid after error).
+ * In case of validation error detected `reason` prop return non-null value and `TextField` must be displayed in `error` state.
+ * This can be used to render appropriate form error.
+ *
+ * [Read the guide](https://next.material-ui-pickers.dev/guides/forms) about form integration and error displaying.
+ * @DateIOType
+ */
+ onError: PropTypes.func,
+ /**
+ * Callback firing on month change. @DateIOType
+ */
+ onMonthChange: PropTypes.func,
+ /**
+ * Callback fired when the popup requests to be opened.
+ * Use in controlled mode (see open).
+ */
+ onOpen: PropTypes.func,
+ /**
+ * Callback fired on view change.
+ */
+ onViewChange: PropTypes.func,
+ /**
+ * Control the popup or dialog open state.
+ */
+ open: PropTypes.bool,
+ /**
+ * Props to pass to keyboard adornment button.
+ */
+ OpenPickerButtonProps: PropTypes.object,
+ /**
+ * Icon displaying for open picker button.
+ */
+ openPickerIcon: PropTypes.node,
+ /**
+ * Force rendering in particular orientation.
+ */
+ orientation: PropTypes.oneOf(['landscape', 'portrait']),
+ /**
+ * Popper props passed down to [Popper](https://material-ui.com/api/popper/) component.
+ */
+ PopperProps: PropTypes.object,
+ /**
+ * Make picker read only.
+ */
+ readOnly: PropTypes.bool,
+ /**
+ * Disable heavy animations.
+ * @default /(android)/i.test(window.navigator.userAgent).
+ */
+ reduceAnimations: PropTypes.bool,
+ /**
+ * Custom renderer for ` ` days. @DateIOType
+ * @example (date, DateRangeDayProps) =>
+ */
+ renderDay: PropTypes.func,
+ /**
+ * The `renderInput` prop allows you to customize the rendered input.
+ * The `startProps` and `endProps` arguments of this render prop contains props of [TextField](https://material-ui.com/api/text-field/#textfield-api),
+ * that you need to forward to the range start/end inputs respectively.
+ * Pay specific attention to the `ref` and `inputProps` keys.
+ * @example
+ * ```jsx
+ * (
+ *
+ *
+ * to
+ *
+ * ;
+ * )}
+ * />
+ * ````
+ */
+ renderInput: PropTypes.func.isRequired,
+ /**
+ * Component displaying when passed `loading` true.
+ * @default () => "..."
+ */
+ renderLoading: PropTypes.func,
+ /**
+ * Custom formatter to be passed into Rifm component.
+ */
+ rifmFormatter: PropTypes.func,
+ /**
+ * Props to pass to right arrow button.
+ */
+ rightArrowButtonProps: PropTypes.object,
+ /**
+ * Right arrow icon aria-label text.
+ */
+ rightArrowButtonText: PropTypes.string,
+ /**
+ * Right arrow icon.
+ */
+ rightArrowIcon: PropTypes.node,
+ /**
+ * Disable specific date. @DateIOType
+ */
+ shouldDisableDate: PropTypes.func,
+ /**
+ * Disable specific years dynamically.
+ * Works like `shouldDisableDate` but for year selection view. @DateIOType.
+ */
+ shouldDisableYear: PropTypes.func,
+ /**
+ * If `true`, days that have `outsideCurrentMonth={true}` are displayed.
+ * @default false
+ */
+ showDaysOutsideCurrentMonth: PropTypes.bool,
+ /**
+ * If `true`, the today button will be displayed. **Note** that `showClearButton` has a higher priority.
+ * @default false
+ */
+ showTodayButton: PropTypes.bool,
+ /**
+ * If `true`, show the toolbar even in desktop mode.
+ */
+ showToolbar: PropTypes.bool,
+ /**
+ * Text for start input label and toolbar placeholder.
+ * @default "Start"
+ */
+ startText: PropTypes.node,
+ /**
+ * "TODAY" Text message
+ * @default "TODAY"
+ */
+ todayText: PropTypes.node,
+ /**
+ * Component that will replace default toolbar renderer.
+ */
+ ToolbarComponent: PropTypes.elementType,
+ /**
+ * Date format, that is displaying in toolbar.
+ */
+ toolbarFormat: PropTypes.string,
+ /**
+ * Mobile picker date value placeholder, displaying if `value` === `null`.
+ * @default "–"
+ */
+ toolbarPlaceholder: PropTypes.node,
+ /**
+ * Mobile picker title, displaying in the toolbar.
+ * @default "SELECT DATE"
+ */
+ toolbarTitle: PropTypes.node,
+ /**
+ * Custom component for popper [Transition](https://material-ui.com/components/transitions/#transitioncomponent-prop).
+ */
+ TransitionComponent: PropTypes.elementType,
+ /**
+ * The value of the picker.
+ */
+ value: PropTypes.arrayOf(
+ PropTypes.oneOfType([
+ PropTypes.any,
+ PropTypes.instanceOf(Date),
+ PropTypes.number,
+ PropTypes.string,
+ ]),
+ ).isRequired,
+};
+
+export type DateRangePickerProps = React.ComponentProps;
+
+export type DateRange = import('./RangeTypes').DateRange;
+
+export default DateRangePicker;
diff --git a/packages/pickers/lib/src/DateRangePicker/DateRangePickerInput.tsx b/packages/material-ui-lab/src/DateRangePicker/DateRangePickerInput.tsx
similarity index 78%
rename from packages/pickers/lib/src/DateRangePicker/DateRangePickerInput.tsx
rename to packages/material-ui-lab/src/DateRangePicker/DateRangePickerInput.tsx
index bf19a1fef51f06..ee8b2469c9ab3b 100644
--- a/packages/pickers/lib/src/DateRangePicker/DateRangePickerInput.tsx
+++ b/packages/material-ui-lab/src/DateRangePicker/DateRangePickerInput.tsx
@@ -1,16 +1,15 @@
import * as React from 'react';
-import * as PropTypes from 'prop-types';
-import { makeStyles } from '@material-ui/core/styles';
-import { useUtils } from '../_shared/hooks/useUtils';
+import { withStyles, WithStyles, createStyles, Theme } from '@material-ui/core/styles';
+import { useUtils } from '../internal/pickers/hooks/useUtils';
import { RangeInput, DateRange, CurrentlySelectingRangeEndProps } from './RangeTypes';
-import { useMaskedInput } from '../_shared/hooks/useMaskedInput';
-import { DateRangeValidationError } from '../_helpers/date-utils';
-import { WrapperVariantContext } from '../wrappers/WrapperVariantContext';
-import { mergeRefs, executeInTheNextEventLoopTick } from '../_helpers/utils';
-import { DateInputProps, MuiTextFieldProps } from '../_shared/PureDateInput';
+import { useMaskedInput } from '../internal/pickers/hooks/useMaskedInput';
+import { DateRangeValidationError } from '../internal/pickers/date-utils';
+import { WrapperVariantContext } from '../internal/pickers/wrappers/WrapperVariantContext';
+import { mergeRefs, executeInTheNextEventLoopTick } from '../internal/pickers/utils';
+import { DateInputProps, MuiTextFieldProps } from '../internal/pickers/PureDateInput';
-export const useStyles = makeStyles(
- (theme) => ({
+export const styles = (theme: Theme) =>
+ createStyles({
root: {
display: 'flex',
alignItems: 'baseline',
@@ -25,9 +24,7 @@ export const useStyles = makeStyles(
margin: '0 16px',
},
},
- }),
- { name: 'MuiPickersDateRangePickerInput' }
-);
+ });
export interface ExportedDateRangePickerInputProps {
/**
@@ -38,14 +35,14 @@ export interface ExportedDateRangePickerInputProps {
* @example
* ```jsx
* (
-
-
- to
-
- ;
- )}
- />
+ * renderInput={(startProps, endProps) => (
+ *
+ *
+ * to
+ *
+ * ;
+ * )}
+ * />
* ````
*/
renderInput: (startProps: MuiTextFieldProps, endProps: MuiTextFieldProps) => React.ReactElement;
@@ -65,7 +62,11 @@ export interface DateRangeInputProps
validationError: DateRangeValidationError;
}
-export const DateRangePickerInput: React.FC = ({
+/**
+ * @ignore - internal component.
+ */
+const DateRangePickerInput: React.FC> = ({
+ classes,
containerRef,
currentlySelectingRangeEnd,
disableOpenPicker,
@@ -86,7 +87,6 @@ export const DateRangePickerInput: React.FC = ({
...other
}) => {
const utils = useUtils();
- const classes = useStyles();
const startRef = React.useRef(null);
const endRef = React.useRef(null);
const wrapperVariant = React.useContext(WrapperVariantContext);
@@ -108,7 +108,7 @@ export const DateRangePickerInput: React.FC = ({
const lazyHandleChangeCallback = React.useCallback(
(...args: Parameters) =>
executeInTheNextEventLoopTick(() => onChange(...args)),
- [onChange]
+ [onChange],
);
const handleStartChange = (date: unknown, inputString?: string) => {
@@ -183,12 +183,4 @@ export const DateRangePickerInput: React.FC = ({
);
};
-DateRangePickerInput.propTypes = {
- acceptRegex: PropTypes.instanceOf(RegExp),
- getOpenDialogAriaText: PropTypes.func,
- mask: PropTypes.string,
- OpenPickerButtonProps: PropTypes.object,
- openPickerIcon: PropTypes.node,
- renderInput: PropTypes.func.isRequired,
- rifmFormatter: PropTypes.func,
-};
+export default withStyles(styles, { name: 'MuiPickersDateRangePickerInput' })(DateRangePickerInput);
diff --git a/packages/material-ui-lab/src/DateRangePicker/DateRangePickerToolbar.tsx b/packages/material-ui-lab/src/DateRangePicker/DateRangePickerToolbar.tsx
new file mode 100644
index 00000000000000..d6a818dba61378
--- /dev/null
+++ b/packages/material-ui-lab/src/DateRangePicker/DateRangePickerToolbar.tsx
@@ -0,0 +1,89 @@
+import * as React from 'react';
+import Typography from '@material-ui/core/Typography';
+import { withStyles, createStyles, WithStyles } from '@material-ui/core/styles';
+import PickersToolbar from '../internal/pickers/PickersToolbar';
+import { useUtils } from '../internal/pickers/hooks/useUtils';
+import PickersToolbarButton from '../internal/pickers/PickersToolbarButton';
+import { ToolbarComponentProps } from '../internal/pickers/typings/BasePicker';
+import { DateRange, CurrentlySelectingRangeEndProps } from './RangeTypes';
+
+export const styles = createStyles({
+ root: {},
+ penIcon: {
+ position: 'relative',
+ top: 4,
+ },
+ dateTextContainer: {
+ display: 'flex',
+ },
+});
+
+interface DateRangePickerToolbarProps
+ extends CurrentlySelectingRangeEndProps,
+ Pick<
+ ToolbarComponentProps,
+ 'isMobileKeyboardViewOpen' | 'toggleMobileKeyboardView' | 'toolbarTitle' | 'toolbarFormat'
+ > {
+ date: DateRange;
+ startText: React.ReactNode;
+ endText: React.ReactNode;
+ currentlySelectingRangeEnd: 'start' | 'end';
+ setCurrentlySelectingRangeEnd: (newSelectingEnd: 'start' | 'end') => void;
+}
+
+/**
+ * @ignore - internal component.
+ */
+const DateRangePickerToolbar: React.FC> = ({
+ classes,
+ currentlySelectingRangeEnd,
+ date: [start, end],
+ endText,
+ isMobileKeyboardViewOpen,
+ setCurrentlySelectingRangeEnd,
+ startText,
+ toggleMobileKeyboardView,
+ toolbarFormat,
+ toolbarTitle = 'SELECT DATE RANGE',
+}) => {
+ const utils = useUtils();
+
+ const startDateValue = start
+ ? utils.formatByString(start, toolbarFormat || utils.formats.shortDate)
+ : startText;
+
+ const endDateValue = end
+ ? utils.formatByString(end, toolbarFormat || utils.formats.shortDate)
+ : endText;
+
+ return (
+
+
+
setCurrentlySelectingRangeEnd('start')}
+ />
+ {'–'}
+ setCurrentlySelectingRangeEnd('end')}
+ />
+
+
+ );
+};
+
+export default withStyles(styles, { name: 'MuiPickersDateRangePickerToolbarProps' })(
+ DateRangePickerToolbar,
+);
diff --git a/packages/pickers/lib/src/DateRangePicker/DateRangePickerView.tsx b/packages/material-ui-lab/src/DateRangePicker/DateRangePickerView.tsx
similarity index 81%
rename from packages/pickers/lib/src/DateRangePicker/DateRangePickerView.tsx
rename to packages/material-ui-lab/src/DateRangePicker/DateRangePickerView.tsx
index 83193362dffbd7..2a73bedbee0c3a 100644
--- a/packages/pickers/lib/src/DateRangePicker/DateRangePickerView.tsx
+++ b/packages/material-ui-lab/src/DateRangePicker/DateRangePickerView.tsx
@@ -1,27 +1,24 @@
import * as React from 'react';
-import * as PropTypes from 'prop-types';
-import { isRangeValid } from '../_helpers/date-utils';
-import { BasePickerProps } from '../typings/BasePicker';
+import PropTypes from 'prop-types';
+import { isRangeValid } from '../internal/pickers/date-utils';
+import { BasePickerProps } from '../internal/pickers/typings/BasePicker';
import { calculateRangeChange } from './date-range-manager';
-import { useUtils, useNow } from '../_shared/hooks/useUtils';
-import { SharedPickerProps } from '../Picker/SharedPickerProps';
-import { DateRangePickerToolbar } from './DateRangePickerToolbar';
-import { useParsedDate } from '../_shared/hooks/date-helpers-hooks';
-import { useCalendarState } from '../views/Calendar/useCalendarState';
+import { useUtils } from '../internal/pickers/hooks/useUtils';
+import { SharedPickerProps } from '../internal/pickers/Picker/SharedPickerProps';
+import DateRangePickerToolbar from './DateRangePickerToolbar';
+import { useCalendarState } from '../DayPicker/useCalendarState';
import { DateRangePickerViewMobile } from './DateRangePickerViewMobile';
-import { defaultMaxDate, defaultMinDate } from '../constants/prop-types';
-import { WrapperVariantContext } from '../wrappers/WrapperVariantContext';
-import { MobileKeyboardInputView } from '../views/MobileKeyboardInputView';
-import { DateRangePickerInput, DateRangeInputProps } from './DateRangePickerInput';
+import { WrapperVariantContext } from '../internal/pickers/wrappers/WrapperVariantContext';
+import { MobileKeyboardInputView } from '../internal/pickers/Picker/Picker';
+import DateRangePickerInput, { DateRangeInputProps } from './DateRangePickerInput';
import { RangeInput, DateRange, CurrentlySelectingRangeEndProps } from './RangeTypes';
-import { ExportedCalendarViewProps, defaultReduceAnimations } from '../views/Calendar/CalendarView';
-import {
- DateRangePickerViewDesktop,
+import { ExportedDayPickerProps, defaultReduceAnimations } from '../DayPicker/DayPicker';
+import DateRangePickerViewDesktop, {
ExportedDesktopDateRangeCalendarProps,
} from './DateRangePickerViewDesktop';
type BaseCalendarPropsToReuse = Omit<
- ExportedCalendarViewProps,
+ ExportedDayPickerProps,
'onYearChange' | 'renderDay'
>;
@@ -31,7 +28,6 @@ export interface ExportedDateRangePickerViewProps
Omit {
/**
* if `true` after selecting `start` date calendar will not automatically switch to the month of `end` date
- *
* @default false
*/
disableAutoMonthSwitching?: boolean;
@@ -46,6 +42,9 @@ interface DateRangePickerViewProps
endText: React.ReactNode;
}
+/**
+ * @ignore - internal component.
+ */
export function DateRangePickerView(props: DateRangePickerViewProps) {
const {
calendars = 2,
@@ -53,14 +52,15 @@ export function DateRangePickerView(props: DateRangePickerViewProps(props: DateRangePickerViewProps();
const utils = useUtils();
const wrapperVariant = React.useContext(WrapperVariantContext);
- const minDate = useParsedDate(unparsedMinDate) as TDate;
- const maxDate = useParsedDate(unparsedMaxDate) as TDate;
const [start, end] = date;
const {
@@ -89,15 +86,16 @@ export function DateRangePickerView(props: DateRangePickerViewProps(props: DateRangePickerViewProps,
wrapperVariant,
- isFullRangeSelected ? 'finish' : 'partial'
+ isFullRangeSelected ? 'finish' : 'partial',
);
},
[
@@ -167,7 +165,7 @@ export function DateRangePickerView(props: DateRangePickerViewProps {
diff --git a/packages/pickers/lib/src/DateRangePicker/DateRangePickerViewDesktop.tsx b/packages/material-ui-lab/src/DateRangePicker/DateRangePickerViewDesktop.tsx
similarity index 78%
rename from packages/pickers/lib/src/DateRangePicker/DateRangePickerViewDesktop.tsx
rename to packages/material-ui-lab/src/DateRangePicker/DateRangePickerViewDesktop.tsx
index d2a3a6e79d3b38..62d31fcd5887ac 100644
--- a/packages/pickers/lib/src/DateRangePicker/DateRangePickerViewDesktop.tsx
+++ b/packages/material-ui-lab/src/DateRangePicker/DateRangePickerViewDesktop.tsx
@@ -1,27 +1,29 @@
import * as React from 'react';
-import { makeStyles } from '@material-ui/core/styles';
+import { withStyles, WithStyles, createStyles, Theme } from '@material-ui/core/styles';
import { DateRange } from './RangeTypes';
-import { useUtils } from '../_shared/hooks/useUtils';
+import { useUtils } from '../internal/pickers/hooks/useUtils';
import { calculateRangePreview } from './date-range-manager';
-import { Calendar, CalendarProps } from '../views/Calendar/Calendar';
-import { DateRangeDay, DateRangeDayProps } from './DateRangePickerDay';
-import { defaultMinDate, defaultMaxDate } from '../constants/prop-types';
-import { ArrowSwitcher, ExportedArrowSwitcherProps } from '../_shared/ArrowSwitcher';
+import PickersCalendar, { PickersCalendarProps } from '../DayPicker/PickersCalendar';
+import DateRangeDay, { DateRangePickerDayProps } from '../DateRangePickerDay';
+import { defaultMinDate, defaultMaxDate } from '../internal/pickers/constants/prop-types';
+import ArrowSwitcher, {
+ ExportedArrowSwitcherProps,
+} from '../internal/pickers/PickersArrowSwitcher';
import {
usePreviousMonthDisabled,
useNextMonthDisabled,
-} from '../_shared/hooks/date-helpers-hooks';
+} from '../internal/pickers/hooks/date-helpers-hooks';
import {
isWithinRange,
isStartOfRange,
isEndOfRange,
DateValidationProps,
-} from '../_helpers/date-utils';
+} from '../internal/pickers/date-utils';
+import { doNothing } from '../internal/pickers/utils';
export interface ExportedDesktopDateRangeCalendarProps {
/**
- * How many calendars render on **desktop** DateRangePicker.
- *
+ * The number of calendars that render on **desktop**.
* @default 2
*/
calendars?: 1 | 2 | 3;
@@ -29,12 +31,12 @@ export interface ExportedDesktopDateRangeCalendarProps {
* Custom renderer for ` ` days. @DateIOType
* @example (date, DateRangeDayProps) =>
*/
- renderDay?: (date: TDate, DateRangeDayProps: DateRangeDayProps) => JSX.Element;
+ renderDay?: (date: TDate, DateRangeDayProps: DateRangePickerDayProps) => JSX.Element;
}
interface DesktopDateRangeCalendarProps
extends ExportedDesktopDateRangeCalendarProps,
- Omit, 'renderDay'>,
+ Omit, 'renderDay' | 'onFocusedDayChange'>,
DateValidationProps,
ExportedArrowSwitcherProps {
date: DateRange;
@@ -42,8 +44,8 @@ interface DesktopDateRangeCalendarProps
currentlySelectingRangeEnd: 'start' | 'end';
}
-export const useStyles = makeStyles(
- (theme) => ({
+export const styles = (theme: Theme) =>
+ createStyles({
root: {
display: 'flex',
flexDirection: 'row',
@@ -63,9 +65,7 @@ export const useStyles = makeStyles(
alignItems: 'center',
justifyContent: 'space-between',
},
- }),
- { name: 'MuiPickersDesktopDateRangeCalendar' }
-);
+ });
function getCalendarsArray(calendars: ExportedDesktopDateRangeCalendarProps['calendars']) {
switch (calendars) {
@@ -81,16 +81,22 @@ function getCalendarsArray(calendars: ExportedDesktopDateRangeCalendarProps(props: DesktopDateRangeCalendarProps) {
+/**
+ * @ignore - internal component.
+ */
+function DateRangePickerViewDesktop