diff --git a/docs/data/date-pickers/base-concepts/base-concepts.md b/docs/data/date-pickers/base-concepts/base-concepts.md index cf175fda55da..636aa7f934ca 100644 --- a/docs/data/date-pickers/base-concepts/base-concepts.md +++ b/docs/data/date-pickers/base-concepts/base-concepts.md @@ -29,6 +29,51 @@ import { DatePicker } from '@mui/x-date-pickers'; import { DatePicker } from '@mui/x-date-pickers-pro'; ``` +## Date library + +The Date and Time Pickers are focused on UI/UX and, like most other picker components available, require a third-party library to format, parse, and mutate dates. + +MUI's components let you choose which library you prefer for this purpose. +This gives you the flexibility to implement any date library you may already be using in your application, without adding an extra one to your bundle. + +To achieve this, both `@mui/x-date-pickers` and `@mui/x-date-pickers-pro` export a set of **adapters** that expose the date manipulation libraries under a unified API. + +### Available libraries + +The Date and Time Pickers currently support the following date libraries: + +- [Day.js](https://day.js.org/) +- [date-fns](https://date-fns.org/) +- [Luxon](https://moment.github.io/luxon/#/) +- [Moment.js](https://momentjs.com/) + +:::info +If you are using a non-Gregorian calendar (such as Jalali or Hijri), please refer to the [Support for other calendar systems](/x/react-date-pickers/calendar-systems/) page. +::: + +### Recommended library + +If you are already using one of the libraries listed above in your application, then you can keep using it with the Date and Time Pickers as well. +This will avoid bundling two libraries. + +If you don't have your own requirements or don't manipulate dates outside of MUI X components, then the recommendation is to use `dayjs` because it has the smallest impact on your application's bundle size. + +Here is the weight added to your gzipped bundle size by each of these libraries when used inside the Date and Time Pickers: + +| Library | Gzipped size | +| :---------------- | -----------: | +| `dayjs@1.11.5` | 6.77 kB | +| `date-fns@2.29.3` | 19.39 kB | +| `luxon@3.0.4` | 23.26 kB | +| `moment@2.29.4` | 20.78 kB | + +:::info +The results above were obtained in October 2022 with the latest version of each library. +The bundling of the JavaScript modules was done by a Create React App, and no locale was loaded for any of the libraries. + +The results may vary in your application depending on the version of each library, the locale, and the bundler used. +::: + ## Other components ### Choose interaction style diff --git a/docs/data/date-pickers/getting-started/getting-started.md b/docs/data/date-pickers/getting-started/getting-started.md index 7572576f22f6..d47244fe608f 100644 --- a/docs/data/date-pickers/getting-started/getting-started.md +++ b/docs/data/date-pickers/getting-started/getting-started.md @@ -24,7 +24,7 @@ Using your favorite package manager, install: :::info If you need more information about the date library supported by the Date and Time Pickers, -take a look at the [dedicated section](/x/react-date-pickers/#date-library) +take a look at the [dedicated section](/x/react-date-pickers/base-concepts/#date-library) ::: The Date and Time Pickers package has a peer dependency on `@mui/material`. diff --git a/docs/data/date-pickers/overview/CommonlyUsedComponents.js b/docs/data/date-pickers/overview/CommonlyUsedComponents.js deleted file mode 100644 index b1ddcc788f64..000000000000 --- a/docs/data/date-pickers/overview/CommonlyUsedComponents.js +++ /dev/null @@ -1,112 +0,0 @@ -import * as React from 'react'; -import { styled } from '@mui/material/styles'; -import Tooltip from '@mui/material/Tooltip'; -import Stack from '@mui/material/Stack'; -import { DemoContainer, DemoItem } from '@mui/x-date-pickers/internals/demo'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import { TimePicker } from '@mui/x-date-pickers/TimePicker'; -import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; -import { DateRangePicker } from '@mui/x-date-pickers-pro/DateRangePicker'; -import { DateTimeRangePicker } from '@mui/x-date-pickers-pro/DateTimeRangePicker'; - -const ProSpan = styled('span')({ - display: 'inline-block', - height: '1em', - width: '1em', - verticalAlign: 'middle', - marginLeft: '0.3em', - marginBottom: '0.08em', - backgroundSize: 'contain', - backgroundRepeat: 'no-repeat', - backgroundImage: 'url(https://mui.com/static/x/pro.svg)', -}); - -function Label({ componentName, valueType, isProOnly }) { - const content = ( - - {componentName} for {valueType} editing - - ); - - if (isProOnly) { - return ( - - - - - - - {content} - - ); - } - - return content; -} - -export default function CommonlyUsedComponents() { - return ( - - - }> - - - }> - - - } - > - - - - } - component="DateRangePicker" - > - - - - } - component="DateTimeRangePicker" - > - - - - - ); -} diff --git a/docs/data/date-pickers/overview/CommonlyUsedComponents.tsx b/docs/data/date-pickers/overview/CommonlyUsedComponents.tsx deleted file mode 100644 index 81fe975bc477..000000000000 --- a/docs/data/date-pickers/overview/CommonlyUsedComponents.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import * as React from 'react'; -import { styled } from '@mui/material/styles'; -import Tooltip from '@mui/material/Tooltip'; -import Stack from '@mui/material/Stack'; -import { DemoContainer, DemoItem } from '@mui/x-date-pickers/internals/demo'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import { TimePicker } from '@mui/x-date-pickers/TimePicker'; -import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; -import { DateRangePicker } from '@mui/x-date-pickers-pro/DateRangePicker'; -import { DateTimeRangePicker } from '@mui/x-date-pickers-pro/DateTimeRangePicker'; - -const ProSpan = styled('span')({ - display: 'inline-block', - height: '1em', - width: '1em', - verticalAlign: 'middle', - marginLeft: '0.3em', - marginBottom: '0.08em', - backgroundSize: 'contain', - backgroundRepeat: 'no-repeat', - backgroundImage: 'url(https://mui.com/static/x/pro.svg)', -}); - -function Label({ - componentName, - valueType, - isProOnly, -}: { - componentName: string; - valueType: string; - isProOnly?: boolean; -}) { - const content = ( - - {componentName} for {valueType} editing - - ); - - if (isProOnly) { - return ( - - - - - - - {content} - - ); - } - - return content; -} - -export default function CommonlyUsedComponents() { - return ( - - - }> - - - }> - - - } - > - - - - } - component="DateRangePicker" - > - - - - } - component="DateTimeRangePicker" - > - - - - - ); -} diff --git a/docs/data/date-pickers/overview/overview.md b/docs/data/date-pickers/overview/overview.md index 8c73477e1a0a..f3cd40be4bc7 100644 --- a/docs/data/date-pickers/overview/overview.md +++ b/docs/data/date-pickers/overview/overview.md @@ -7,68 +7,16 @@ materialDesign: https://m2.material.io/components/date-pickers waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/datepicker-dialog/ --- -# MUI X Date and Time Pickers - -

These react date picker and time picker components let users select date or time values.

- -{{"component": "@mui/docs/ComponentLinkHeader"}} - -## Overview - -{{"demo": "CommonlyUsedComponents.js"}} - -## Community or Pro plan? - -The Date and Time Pickers are available in two packages: - -- `@mui/x-date-pickers`, which is MIT licensed (free forever) and contains all the components to edit a date and/or a time. -- `@mui/x-date-pickers-pro`, which is [commercially licensed](/x/introduction/licensing/#pro-plan) and contains additional components to edit date and/or time ranges. - -## Date library - -The Date and Time Pickers are focused on UI/UX and, like most other picker components available, require a third-party library to format, parse, and mutate dates. - -MUI's components let you choose which library you prefer for this purpose. -This gives you the flexibility to implement any date library you may already be using in your application, without adding an extra one to your bundle. +{{"component": "modules/components/overview/XLogo.tsx"}} -To achieve this, both `@mui/x-date-pickers` and `@mui/x-date-pickers-pro` export a set of **adapters** that expose the date manipulation libraries under a unified API. - -### Available libraries - -The Date and Time Pickers currently support the following date libraries: - -- [Day.js](https://day.js.org/) -- [date-fns](https://date-fns.org/) -- [Luxon](https://moment.github.io/luxon/#/) -- [Moment.js](https://momentjs.com/) - -:::info -If you are using a non-Gregorian calendar (such as Jalali or Hijri), please refer to the [Support for other calendar systems](/x/react-date-pickers/calendar-systems/) page. -::: - -### Recommended library - -If you are already using one of the libraries listed above in your application, then you can keep using it with the Date and Time Pickers as well. -This will avoid bundling two libraries. - -If you don't have your own requirements or don't manipulate dates outside of MUI X components, then the recommendation is to use `dayjs` because it has the smallest impact on your application's bundle size. - -Here is the weight added to your gzipped bundle size by each of these libraries when used inside the Date and Time Pickers: - -| Library | Gzipped size | -| :---------------- | -----------: | -| `dayjs@1.11.5` | 6.77 kB | -| `date-fns@2.29.3` | 19.39 kB | -| `luxon@3.0.4` | 23.26 kB | -| `moment@2.29.4` | 20.78 kB | - -:::info -The results above were obtained in October 2022 with the latest version of each library. -The bundling of the JavaScript modules was done by a Create React App, and no locale was loaded for any of the libraries. +# MUI X Date and Time Pickers -The results may vary in your application depending on the version of each library, the locale, and the bundler used. -::: +

A collection of React UI components for selecting dates, times, and ranges.

-## What's next? +{{"component": "modules/components/overview/MainDemo.tsx"}} -Continue to the [next page](/x/react-date-pickers/getting-started/) and learn how to prepare your application for the Date and Time Pickers. +{{"component": "modules/components/overview/FeatureHighlight.tsx"}} +{{"component": "modules/components/overview/CommunityOrPro.tsx"}} +{{"component": "modules/components/overview/Keyboard.tsx"}} +{{"component": "modules/components/overview/Internationalization.tsx"}} +{{"component": "modules/components/overview/DateLibraries.tsx"}} diff --git a/docs/pages/x/react-date-pickers/index.js b/docs/pages/x/react-date-pickers/index.js index b3933fe81dc2..204ca09b82db 100644 --- a/docs/pages/x/react-date-pickers/index.js +++ b/docs/pages/x/react-date-pickers/index.js @@ -3,5 +3,5 @@ import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; import * as pageProps from 'docsx/data/date-pickers/overview/overview.md?muiMarkdown'; export default function Page() { - return ; + return ; } diff --git a/docs/public/static/x/date-libraries/datefns.png b/docs/public/static/x/date-libraries/datefns.png new file mode 100644 index 000000000000..ab1beba7507e Binary files /dev/null and b/docs/public/static/x/date-libraries/datefns.png differ diff --git a/docs/public/static/x/date-libraries/dayjs.png b/docs/public/static/x/date-libraries/dayjs.png new file mode 100644 index 000000000000..1eb9630fbee3 Binary files /dev/null and b/docs/public/static/x/date-libraries/dayjs.png differ diff --git a/docs/public/static/x/date-libraries/luxon.png b/docs/public/static/x/date-libraries/luxon.png new file mode 100644 index 000000000000..41e6bc0d5766 Binary files /dev/null and b/docs/public/static/x/date-libraries/luxon.png differ diff --git a/docs/public/static/x/date-libraries/momentjs.png b/docs/public/static/x/date-libraries/momentjs.png new file mode 100644 index 000000000000..32d3732df27c Binary files /dev/null and b/docs/public/static/x/date-libraries/momentjs.png differ diff --git a/docs/src/modules/components/overview/CommunityOrPro.tsx b/docs/src/modules/components/overview/CommunityOrPro.tsx new file mode 100644 index 000000000000..7e6dcbf3de21 --- /dev/null +++ b/docs/src/modules/components/overview/CommunityOrPro.tsx @@ -0,0 +1,81 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Divider from '@mui/material/Divider'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; +import InfoCard from './InfoCard'; + +export default function CommunityOrPro() { + return ( + + + + + + + Community and Pro + + + Two packages for every need + + + Start with the free-forever Community version, then upgrade to Pro when you are ready + for additional features and components. + + + + + + + + } + description={[ + 'Free forever under an MIT license. Includes Date Pickers, Time Pickers, and Date Time Pickers.', + ]} + backgroundColor="subtle" + link="/pricing" + /> + + + } + description={[ + 'Requires a commercial license. Includes all Community components plus the Date and Time Range Pickers.', + ]} + backgroundColor="subtle" + link="/pricing" + /> + + + + + ); +} diff --git a/docs/src/modules/components/overview/DateLibraries.tsx b/docs/src/modules/components/overview/DateLibraries.tsx new file mode 100644 index 000000000000..139365eac622 --- /dev/null +++ b/docs/src/modules/components/overview/DateLibraries.tsx @@ -0,0 +1,114 @@ +import * as React from 'react'; +// @ts-ignore +import SectionHeadline from 'docs/src/components/typography/SectionHeadline'; +import { HighlightedCode } from '@mui/docs/HighlightedCode'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Divider from '@mui/material/Divider'; +import Stack from '@mui/material/Stack'; +import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; +import ToggleButton from '@mui/material/ToggleButton'; +import Typography from '@mui/material/Typography'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; + +const dateLibraries = [ + { + name: 'Dayjs', + link: '/static/x/date-libraries/dayjs.png', + adapter: 'AdapterDayjs', + value: "dayjs('2024-04-17')", + }, + { + name: 'Luxon', + link: '/static/x/date-libraries/luxon.png', + adapter: 'AdapterLuxon', + value: "DateTime.fromISO('2024-04-17')", + }, + { + name: 'date-fns', + link: '/static/x/date-libraries/datefns.png', + adapter: 'AdapterDateFns', + value: "new Date('2024-04-17')", + }, + { + name: 'Moment.js', + link: '/static/x/date-libraries/momentjs.png', + adapter: 'AdapterMoment', + value: "moment('2024-04-17')", + }, +]; + +export default function DateLibraries() { + const [selectedLibrary, setSelectedLibrary] = React.useState(0); + + const handleLibrarySwitch = (_event: React.MouseEvent, library: number) => { + if (library !== null) { + setSelectedLibrary(library); + } + }; + return ( + + + + + + Use your favorite date library + + } + description="MUI X Date Pickers integrate smoothly with the most popular date libraries available." + /> + + + + + {dateLibraries.map((library, index) => ( + + {library.name} + {library.name} + + ))} + + + + + `, + ` `, + ``, + ].join('\n')} + language="jsx" + copyButtonHidden + /> + + + + + ); +} diff --git a/docs/src/modules/components/overview/FeatureHighlight.tsx b/docs/src/modules/components/overview/FeatureHighlight.tsx new file mode 100644 index 000000000000..684d7972340e --- /dev/null +++ b/docs/src/modules/components/overview/FeatureHighlight.tsx @@ -0,0 +1,72 @@ +import * as React from 'react'; +import Divider from '@mui/material/Divider'; +import Typography from '@mui/material/Typography'; +import Stack from '@mui/material/Stack'; +import FormatPaintIcon from '@mui/icons-material/FormatPaint'; +import AccessibilityNewIcon from '@mui/icons-material/AccessibilityNew'; +import LanguageIcon from '@mui/icons-material/Language'; +import InfoCard from './InfoCard'; + +const featuredItems = [ + { + title: 'Highly customizable', + description: + 'Start with our meticulous Material Design implementation, or go fully custom with your own design system.', + icon: , + }, + { + title: 'Accessibility', + description: + 'We are committed to meeting or exceeding global standards for accessibility, and we provide thorough guidance on best practices in our documentation.', + icon: , + }, + { + title: 'Internationalization', + description: + 'Serve the needs of users all around the world with built-in support for multiple time zones, languages, and date formats.', + icon: , + }, +]; + +export default function FeatureHighlight() { + return ( + + + + + + Using MUI X Date Pickers + + + First-class developer experience + + + MUI X Date and Time Pickers are designed to be delightful and intuitive for developers + and users alike. + + + + {featuredItems.map(({ title, description, icon }, index) => ( + + ))} + + + + ); +} diff --git a/docs/src/modules/components/overview/InfoCard.tsx b/docs/src/modules/components/overview/InfoCard.tsx new file mode 100644 index 000000000000..aaaebb419a61 --- /dev/null +++ b/docs/src/modules/components/overview/InfoCard.tsx @@ -0,0 +1,107 @@ +import * as React from 'react'; +import { alpha } from '@mui/material/styles'; +import Link from '@mui/material/Link'; +import Typography from '@mui/material/Typography'; +import Stack from '@mui/material/Stack'; +import Paper from '@mui/material/Paper'; + +type InfoCardProps = { + title: string; + description?: string | string[]; + icon?: React.ReactNode; + onClick?: () => void; + active?: boolean; + backgroundColor?: 'gradient' | 'subtle'; + link?: string; +}; +export default function InfoCard({ + title, + description, + icon: Icon, + onClick, + active, + backgroundColor = 'gradient', + link, +}: InfoCardProps) { + const clickable = Boolean(onClick); + + return ( + ({ + p: 2.5, + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + textAlign: 'left', + flexGrow: 1, + height: '100%', + boxShadow: 'transparent', + background: + backgroundColor === 'gradient' + ? `${(theme.vars || theme).palette.gradients.linearSubtle}` + : 'transparent', + ...(clickable && { + cursor: 'pointer', + '&:hover': { + opacity: 1, + boxShadow: `0px 2px 30px 0px ${alpha(theme.palette.primary[50], 0.3)} inset, 0px 1px 6px 0px ${theme.palette.primary[100]}`, + borderColor: 'primary.100', + ...(active && { + borderColor: 'primary.300', + }), + }, + ...(active && { + boxShadow: `0px 2px 30px 0px ${alpha(theme.palette.primary[50], 0.3)} inset, 0px 1px 6px 0px ${theme.palette.primary[100]}`, + borderColor: 'primary.200', + }), + ...(!active && { borderColor: 'grey.300', opacity: 0.7 }), + }), + ...theme.applyDarkStyles({ + bgcolor: alpha(theme.palette.primaryDark[800], 0.25), + background: `${(theme.vars || theme).palette.gradients.linearSubtle}`, + borderColor: 'primaryDark.700', + ...(clickable && { + '&:hover': { + boxShadow: `0px 2px 30px 0px ${alpha(theme.palette.primary[800], 0.1)} inset, 0px 1px 6px 0px ${theme.palette.primary[900]}`, + + borderColor: 'primary.300', + }, + ...(active && { + boxShadow: `0px 2px 30px 0px ${alpha(theme.palette.primary[800], 0.1)} inset, 0px 1px 6px 0px ${theme.palette.primary[900]}`, + borderColor: 'primary.100', + }), + }), + }), + })} + > + + {Icon} + + {title} + + + {description && typeof description === 'string' && ( + + {description} + + )} + {description && + typeof description === 'object' && + description.map((item, index) => ( + + {item} + + ))} + + ); +} diff --git a/docs/src/modules/components/overview/Internationalization.tsx b/docs/src/modules/components/overview/Internationalization.tsx new file mode 100644 index 000000000000..5f5353a4e952 --- /dev/null +++ b/docs/src/modules/components/overview/Internationalization.tsx @@ -0,0 +1,387 @@ +import * as React from 'react'; +import dayjs from 'dayjs'; +import 'dayjs/locale/ro'; +import 'dayjs/locale/zh-cn'; +import utc from 'dayjs/plugin/utc'; +import timezone from 'dayjs/plugin/timezone'; +import { styled, createTheme, ThemeProvider, useTheme } from '@mui/material/styles'; +// @ts-ignore +import SectionHeadline from 'docs/src/components/typography/SectionHeadline'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Divider from '@mui/material/Divider'; +import Paper from '@mui/material/Paper'; +import Stack from '@mui/material/Stack'; +import MuiToggleButtonGroup, { toggleButtonGroupClasses } from '@mui/material/ToggleButtonGroup'; +import MuiToggleButton from '@mui/material/ToggleButton'; +import Typography from '@mui/material/Typography'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; +import { + DateTimeRangePicker, + DateTimeField, + DatePicker, + DateTimeValidationError, + DateCalendar, +} from '@mui/x-date-pickers-pro'; +import { AdapterDayjs } from '@mui/x-date-pickers-pro/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers-pro/LocalizationProvider'; +import { roRO, enUS, zhCN } from '@mui/x-date-pickers-pro/locales'; +import InfoCard from './InfoCard'; +import WorldMapSvg, { ContinentClickHandler } from './WorldMapSvg'; + +dayjs.extend(utc); +dayjs.extend(timezone); + +const internationalizationFeatures = [ + { + title: 'Support for multiple timezones', + description: 'Accommodate global users and events in any geographical location.', + }, + { + title: 'Support for multiple languages', + description: + "Meet users where they're at with support for common date formats and languages used around the world.", + }, + { + title: 'Validation and error handling', + description: 'We have all use cases covered for you and your end users.', + }, +]; + +function DemoWrapper({ + children, + controls: ToolbarControls, + link, +}: { + children: React.ReactNode; + controls?: React.ReactNode; + link: string; +}) { + return ( + ({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + border: '1px solid', + borderColor: 'divider', + borderRadius: 1, + flexGrow: 1, + width: '100%', + justifyContent: 'space-between', + background: (brandingTheme.vars || brandingTheme).palette.gradients.linearSubtle, + })} + > + {children} + + ({ + width: '100%', + border: '1px solid transparent', + borderTopColor: 'divider', + borderTopLeftRadius: 0, + borderTopRightRadius: 0, + display: 'flex', + padding: brandingTheme.spacing(1), + justifyContent: 'flex-end', + alignItems: { md: 'center' }, + gap: 2, + })} + > + {ToolbarControls} + + + + ); +} + +function TimezonesDemo() { + const [selectedTimezone, setSelectedTimezone] = React.useState(null); + const brandingTheme = useTheme(); + const theme = createTheme({ palette: { mode: brandingTheme.palette.mode } }); + + const handleContinentClick: ContinentClickHandler = (e, newTimezone) => { + if (selectedTimezone === newTimezone) { + setSelectedTimezone(null); + } else { + setSelectedTimezone(newTimezone); + } + }; + + return ( + + + + + {selectedTimezone ? `Selected timezone: ${selectedTimezone}` : 'Select timezone'} + + + + + + + + + + + + + ); +} + +type Languages = 'en' | 'ro' | 'zh-cn'; +const locales = { + ro: roRO, + en: enUS, + 'zh-cn': zhCN, +}; + +const ToggleButton = styled(MuiToggleButton)({ + borderColor: 'transparent', + padding: '5px 8px', +}); +const ToggleButtonGroup = styled(MuiToggleButtonGroup)(({ theme }) => ({ + gap: theme.spacing(1), + [`& .${toggleButtonGroupClasses.firstButton}, & .${toggleButtonGroupClasses.lastButton},& .${toggleButtonGroupClasses.middleButton} `]: + { + borderRadius: theme.shape.borderRadius, + }, +})); + +function Controls({ + selectedLanguage, + handleLanguageSwitch, +}: { + selectedLanguage: Languages; + handleLanguageSwitch: (event: React.MouseEvent, newLanguage: Languages) => void; +}) { + return ( + + + + Română + + + English + + + 日本語 + + + + ); +} + +function LanguagesDemo() { + const brandingTheme = useTheme(); + const [selectedLanguage, setSelectedLanguage] = React.useState('zh-cn'); + + const theme = createTheme({ palette: { mode: brandingTheme.palette.mode } }); + + const handleLanguageSwitch = (_event: React.MouseEvent, newLanguage: Languages) => { + if (newLanguage !== null) { + setSelectedLanguage(newLanguage); + } + }; + + return ( + + + } + link="/x/react-date-pickers/localization" + > + + + + + + + + + + + + ); +} + +const startOfQ12022 = dayjs('2024-01-01T00:00:00.000'); +const endOfQ12022 = dayjs('2024-03-31T23:59:59.999'); +const fiveAM = dayjs().set('hour', 5).startOf('hour'); +const nineAM = dayjs().set('hour', 9).startOf('hour'); + +const getError = (error: DateTimeValidationError | null) => { + switch (error) { + case 'maxDate': + case 'minDate': { + return 'Please select a date in the first quarter of 2024'; + } + + case 'invalidDate': { + return 'Your date is not valid'; + } + + default: { + return ''; + } + } +}; + +function ValidationDemo() { + const brandingTheme = useTheme(); + const [fieldError, setFieldError] = React.useState(null); + const [pickerError, setPickerError] = React.useState(null); + + const theme = createTheme({ palette: { mode: brandingTheme.palette.mode } }); + + return ( + + + + + setFieldError(newError)} + slotProps={{ + textField: { + helperText: getError(fieldError), + fullWidth: true, + }, + }} + minDate={startOfQ12022} + maxDate={endOfQ12022} + /> + setPickerError(newError)} + slotProps={{ + textField: { + helperText: getError(pickerError), + fullWidth: true, + }, + }} + minDate={startOfQ12022} + maxDate={endOfQ12022} + /> + + + + + + ); +} + +export default function Internationalization() { + const [activeItem, setActiveItem] = React.useState(0); + + return ( + + + + + + {internationalizationFeatures[activeItem].title} + + } + /> + {internationalizationFeatures.map(({ title, description }, index) => ( + setActiveItem(index)} + backgroundColor="subtle" + /> + ))} + + + {activeItem === 0 && } + {activeItem === 1 && } + {activeItem === 2 && } + + + + ); +} diff --git a/docs/src/modules/components/overview/Keyboard.tsx b/docs/src/modules/components/overview/Keyboard.tsx new file mode 100644 index 000000000000..a587bf3f0164 --- /dev/null +++ b/docs/src/modules/components/overview/Keyboard.tsx @@ -0,0 +1,503 @@ +import * as React from 'react'; +import clsx from 'clsx'; +import dayjs from 'dayjs'; +// @ts-ignore +import SectionHeadline from 'docs/src/components/typography/SectionHeadline'; +import Button from '@mui/material/Button'; +import Divider from '@mui/material/Divider'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; +import { styled, alpha, createTheme, ThemeProvider, useTheme } from '@mui/material/styles'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { DateField } from '@mui/x-date-pickers/DateField'; +import { FieldSelectedSections } from '@mui/x-date-pickers/models'; + +type KeyType = { + label: string; + showLabel?: boolean; + width?: number; + displayedLabel?: string; + location?: number; + shouldSelect?: boolean; + code?: string; + keyCode?: number; + height?: number; + x?: number; + y?: number; + keyType?: 'navigate-left' | 'navigate-right' | 'input'; +}; + +const keys: KeyType[][] = [ + [ + { label: 'Escape', width: 43, showLabel: true, displayedLabel: 'Esc', shouldSelect: true }, + { label: 'f1', width: 23, showLabel: false }, + { label: 'f2', width: 23, showLabel: false }, + { label: 'f3', width: 23, showLabel: false }, + { label: 'f4', width: 23, showLabel: false }, + { label: 'f5', width: 23, showLabel: false }, + { label: 'f6', width: 23, showLabel: false }, + { label: 'f7', width: 23, showLabel: false }, + { label: 'f8', width: 23, showLabel: false }, + { label: 'f9', width: 23, showLabel: false }, + { label: 'f10', width: 23, showLabel: false }, + { label: 'f11', width: 23, showLabel: false }, + { label: 'f12', width: 23, showLabel: false }, + { label: 'Delete', width: 23, showLabel: true, shouldSelect: true, displayedLabel: 'Del' }, + ], + [ + { label: '`', width: 23, showLabel: true }, + { + label: '1', + width: 23, + showLabel: true, + shouldSelect: true, + code: 'Digit1', + keyCode: 49, + keyType: 'input', + }, + { + label: '2', + width: 23, + showLabel: true, + shouldSelect: true, + code: 'Digit2', + keyCode: 50, + keyType: 'input', + }, + { + label: '3', + width: 23, + showLabel: true, + shouldSelect: true, + code: 'Digit3', + keyCode: 51, + keyType: 'input', + }, + { + label: '4', + width: 23, + showLabel: true, + shouldSelect: true, + code: 'Digit4', + keyCode: 52, + keyType: 'input', + }, + { + label: '5', + width: 23, + showLabel: true, + shouldSelect: true, + code: 'Digit5', + keyCode: 53, + keyType: 'input', + }, + { + label: '6', + width: 23, + showLabel: true, + shouldSelect: true, + code: 'Digit6', + keyCode: 54, + keyType: 'input', + }, + { + label: '7', + width: 23, + showLabel: true, + shouldSelect: true, + code: 'Digit7', + keyCode: 55, + keyType: 'input', + }, + { + label: '8', + width: 23, + showLabel: true, + shouldSelect: true, + code: 'Digit8', + keyCode: 56, + keyType: 'input', + }, + { + label: '9', + width: 23, + showLabel: true, + shouldSelect: true, + code: 'Digit9', + keyCode: 57, + keyType: 'input', + }, + { + label: '0', + width: 23, + showLabel: true, + shouldSelect: true, + code: 'Digit0', + keyCode: 58, + keyType: 'input', + }, + { label: '-', width: 23, showLabel: false }, + { label: '=', width: 23, showLabel: false }, + { + label: 'Backspace', + displayedLabel: 'Back', + width: 43, + showLabel: true, + code: 'Backspace', + keyCode: 8, + shouldSelect: true, + }, + ], + [ + { label: 'Tab', width: 43, showLabel: true, shouldSelect: true }, + { label: 'q', width: 23, showLabel: false }, + { label: 'w', width: 23, showLabel: false }, + { label: 'e', width: 23, showLabel: false }, + { label: 'r', width: 23, showLabel: false }, + { label: 't', width: 23, showLabel: false }, + { label: 'y', width: 23, showLabel: false }, + { label: 'u', width: 23, showLabel: false }, + { label: 'i', width: 23, showLabel: false }, + { label: 'o', width: 23, showLabel: false }, + { label: 'p', width: 23, showLabel: false }, + { label: '[', width: 23, showLabel: false }, + { label: ']', width: 23, showLabel: false }, + { label: '\\', width: 23, showLabel: false }, + ], + [ + { label: 'CapsLock', width: 49, showLabel: true, displayedLabel: 'Caps', shouldSelect: true }, + { label: 'a', width: 23, showLabel: false }, + { label: 's', width: 23, showLabel: false }, + { label: 'd', width: 23, showLabel: false }, + { label: 'f', width: 23, showLabel: false }, + { label: 'g', width: 23, showLabel: false }, + { label: 'h', width: 23, showLabel: false }, + { label: 'j', width: 23, showLabel: false }, + { label: 'k', width: 23, showLabel: false }, + { label: 'l', width: 23, showLabel: false }, + { label: ';', width: 23, showLabel: false }, + { label: "'", width: 23, showLabel: false }, + { label: 'Enter', width: 45, showLabel: true, shouldSelect: true }, + ], + [ + { label: 'Shift', width: 59, showLabel: true, location: 1 }, + { label: 'z', width: 23, showLabel: false }, + { label: 'x', width: 23, showLabel: false }, + { label: 'c', width: 23, showLabel: false }, + { label: 'v', width: 23, showLabel: false }, + { label: 'b', width: 23, showLabel: false }, + { label: 'n', width: 23, showLabel: false }, + { label: 'm', width: 23, showLabel: false }, + { label: ',', width: 23, showLabel: false }, + { label: '.', width: 23, showLabel: false }, + { label: '/', width: 23, showLabel: true }, + { label: 'Shift', width: 63, showLabel: true, location: 2 }, + ], + [ + { label: 'Control', width: 43, showLabel: true, location: 1, displayedLabel: 'Ctrl' }, + { label: 'Meta', width: 29, showLabel: false }, + { label: 'Alt', width: 42, showLabel: true, location: 1 }, + { label: ' ', width: 119, showLabel: false, displayedLabel: 'Space' }, + { label: 'Alt', width: 42, showLabel: true, location: 2 }, + { label: 'Control', width: 23, showLabel: true, location: 2, displayedLabel: 'Ctrl' }, + ], +]; + +const arrowKeys: KeyType[] = [ + { + label: 'ArrowLeft', + width: 23, + height: 9, + showLabel: false, + shouldSelect: true, + x: 340.5, + y: 166.5, + keyType: 'navigate-left', + }, + { + label: 'ArrowUp', + width: 23, + height: 9, + showLabel: false, + shouldSelect: true, + x: 368.5, + y: 152.5, + }, + { + label: 'ArrowDown', + width: 23, + height: 9, + showLabel: false, + shouldSelect: true, + x: 368.5, + y: 166.5, + }, + { + label: 'ArrowRight', + width: 23, + height: 9, + showLabel: false, + shouldSelect: true, + x: 396.5, + y: 166.5, + keyType: 'navigate-right', + }, +]; + +const RootRectangle = styled('rect')(({ theme }) => ({ + fill: 'white', + stroke: theme.palette.grey[500], + ...(theme.palette.mode === 'dark' && { + stroke: theme.palette.grey[600], + fill: theme.palette.background.paper, + }), +})); +const KeyRoot = styled('g')(({ theme }) => ({ + cursor: 'pointer', + '&:not(.selected):hover ': { '& .key-rect': { fill: theme.palette.action.hover } }, + '&.selected': { '& .key-rect': { fill: alpha(theme.palette.primary.main, 0.2) } }, +})); +const KeyRectangle = styled('rect')(({ theme }) => ({ + fill: 'white', + stroke: theme.palette.grey[500], + ...(theme.palette.mode === 'dark' && { + stroke: theme.palette.grey[600], + fill: theme.palette.background.paper, + }), +})); +const KeyText = styled('text')(({ theme }) => ({ + fill: theme.palette.grey[800], + fontSize: 9, + fontFamily: 'IBM Plex Sans', + ...(theme.palette.mode === 'dark' && { fill: theme.palette.text.primary }), +})); + +type KeyboardSvgProps = { + handleKeySelection: HandleKeySelection; + selectedKey: SelectedKey | null; +}; + +export function KeyboardSvg({ selectedKey, handleKeySelection }: KeyboardSvgProps) { + return ( + + + + + + {keys.map((row, rowIndex) => { + let xPosition = 12.5; + const yPosition = 12.5 + rowIndex * 28; + return ( + + {row.map( + ( + { + label, + displayedLabel, + showLabel, + width = 23, + location = 0, + shouldSelect = false, + code, + keyCode, + }, + keyIndex, + ) => { + const textXPosition = xPosition + width / 2; + const textYPosition = yPosition + 11.5; + const keyComponent = ( + { + if (shouldSelect) { + e.preventDefault(); + + handleKeySelection(e, { + key: label, + location: location || 0, + code: code || label, + keyCode: keyCode || 0, + }); + } + }} + onMouseUp={(e) => { + if (shouldSelect) { + handleKeySelection(e, null); + } + }} + > + + {showLabel && ( + + {displayedLabel || label} + + )} + + ); + xPosition = xPosition + width + 5; + + return keyComponent; + }, + )} + + ); + })} + {arrowKeys.map( + ({ label: key, location = 0, code = key, keyCode, width, height, x, y }, keyIndex) => { + return ( + { + e.preventDefault(); + handleKeySelection(e, { key, location, code, keyCode }); + }} + onMouseUp={(e) => { + handleKeySelection(e, null); + }} + > + + + ); + }, + )} + + ); +} + +type SelectedKey = { + key: string; + location?: number; + code?: string; + keyCode?: number; +}; +type HandleKeySelection = (e: React.SyntheticEvent, key: SelectedKey | null) => void; + +export default function Keyboard() { + const [selectedKey, setSelectedKey] = React.useState(null); + const ref = React.useRef(null); + const selectedSection = React.useRef(0); + + const brandingTheme = useTheme(); + const theme = createTheme({ palette: { mode: brandingTheme.palette.mode } }); + + const handleKeySelection = (e: React.SyntheticEvent, key: SelectedKey | null) => { + const sectionContent = (ref.current as any).querySelector( + `.MuiPickersSectionList-section[data-sectionindex="${selectedSection.current || 0}"] .MuiPickersSectionList-sectionContent`, + ); + sectionContent.focus(); + + if (key) { + const event = new KeyboardEvent('keydown', { + ...key, + bubbles: true, + cancelable: true, + }); + + sectionContent.dispatchEvent(event); + + if (key.key === 'Backspace') { + sectionContent.textContent = ''; + const inputEvent = new InputEvent('input', { + data: '', + inputType: 'insertText', + bubbles: true, + cancelable: true, + }); + + sectionContent.dispatchEvent(inputEvent); + } else if (key?.keyCode && key?.keyCode >= 49 && key?.keyCode <= 58) { + sectionContent.textContent = key.key; + const inputEvent = new InputEvent('input', { + data: key.key, + inputType: 'insertText', + bubbles: true, + cancelable: true, + }); + sectionContent.dispatchEvent(inputEvent); + } + } + setSelectedKey(key); + }; + + return ( + + + + + + Assistive technology support + + } + description="The MUI X Date Pickers feature advanced keyboard support that's compliant with WCAG and WAI-ARIA standards, so users who require assistive technology can navigate your interface with ease." + /> + + + + + + { + setSelectedKey({ + key: e.key, + code: e.code, + location: e.location, + }); + }} + onKeyUp={() => { + setSelectedKey(null); + }} + enableAccessibleFieldDOMStructure + onSelectedSectionsChange={(newSelectedSection) => { + selectedSection.current = newSelectedSection; + }} + /> + + + + + + ); +} diff --git a/docs/src/modules/components/overview/MainDemo.tsx b/docs/src/modules/components/overview/MainDemo.tsx new file mode 100644 index 000000000000..279e80189673 --- /dev/null +++ b/docs/src/modules/components/overview/MainDemo.tsx @@ -0,0 +1,125 @@ +import * as React from 'react'; +import { useTheme, ThemeProvider, createTheme, Theme, Components } from '@mui/material/styles'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import Stack from '@mui/material/Stack'; +import Paper from '@mui/material/Paper'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import FlightPicker from './mainDemo/FlightPicker'; +import ThemeToggleGroup from './mainDemo/ThemeToggleGroup'; +import Clock from './mainDemo/Clock'; +import Birthday from './mainDemo/Birthday'; +import DigitalClock from './mainDemo/DigitalClock'; +import DateRangeWithShortcuts from './mainDemo/DateRangeWithShortcuts'; +import PickerButton from './mainDemo/PickerButton'; +import '@mui/x-date-pickers-pro/themeAugmentation'; + +const components: Components = { + MuiPickersDay: { + styleOverrides: { + root: { + fontWeight: 400, + }, + today: ({ theme }) => ({ + fontWeight: 600, + borderColor: theme.palette.primary.light, + }), + }, + }, + MuiPickersMonth: { + styleOverrides: { + monthButton: ({ theme }) => ({ + fontWeight: 400, + fontSize: '0.875rem', + borderRadius: theme.shape.borderRadius, + height: 28, + width: 64, + }), + }, + }, + MuiPickersYear: { + styleOverrides: { + yearButton: ({ theme }) => ({ + fontWeight: 400, + fontSize: '0.875rem', + borderRadius: theme.shape.borderRadius, + height: 28, + width: 64, + }), + }, + }, + MuiPickersToolbar: { styleOverrides: { content: { justifyContent: 'space-between' } } }, + MuiTimePickerToolbar: { + styleOverrides: { + ampmSelection: { marginRight: 0 }, + }, + }, + MuiButton: { + styleOverrides: { + root: ({ theme, ownerState }) => ({ + ...(ownerState.size === 'medium' && { + padding: theme.spacing('6px', '8px'), + }), + }), + }, + }, +}; + +export default function MainDemo() { + const brandingTheme = useTheme(); + const isMobile = useMediaQuery(brandingTheme.breakpoints.down('sm')); + const isTablet = useMediaQuery(brandingTheme.breakpoints.up('md')); + const isDesktop = useMediaQuery(brandingTheme.breakpoints.up('xl')); + + const [showCustomTheme, setShowCustomTheme] = React.useState(false); + + const toggleCustomTheme = () => { + setShowCustomTheme((prev) => !prev); + }; + + const theme = createTheme({ palette: { mode: brandingTheme.palette.mode } }); + const customTheme = createTheme(brandingTheme, { + components, + shape: { borderRadius: 8 }, + typography: { fontFamily: ['"Inter", "sans-serif"'].join(',') }, + }); + + return ( + + + + + + + + + + + + {isTablet && ( + + + + + )} + + + {isDesktop && ( + + + + + )} + + + + + ); +} diff --git a/docs/src/modules/components/overview/WorldMapSvg.tsx b/docs/src/modules/components/overview/WorldMapSvg.tsx new file mode 100644 index 000000000000..9818f694d745 --- /dev/null +++ b/docs/src/modules/components/overview/WorldMapSvg.tsx @@ -0,0 +1,123 @@ +import * as React from 'react'; +import { styled, useTheme } from '@mui/material/styles'; +import clsx from 'clsx'; + +const Continent = styled('g')(({ theme }) => ({ + cursor: 'pointer', + opacity: 0.9, + '&:hover ': { opacity: 1 }, + '&.selected': { + fill: theme.palette.primary.main, + }, +})); + +export type ContinentClickHandler = (event: React.MouseEvent, timezone: string) => void; + +type WorldMapSvgProps = { + onClickContinent: ContinentClickHandler; + selectedTimezone: string | null; +}; + +export default function WorldMapSvg({ onClickContinent, selectedTimezone }: WorldMapSvgProps) { + const brandingTheme = useTheme(); + + const getMapBaseColor = (lightness: number, opacity: number = 1) => { + return brandingTheme.palette.mode === 'light' + ? `hsla(210,20%,${lightness}%, ${opacity * 100}%)` + : `hsla(210,20%,${lightness}%, ${opacity * 100}%)`; + }; + + const timezones: { [key: string]: string } = { + europe: 'Europe/Paris', + asia: 'Asia/Hong_kong', + southAmerica: 'America/Sao_Paulo', + northAmerica: 'America/Chicago', + africa: 'Africa/Casablanca', + australia: 'Australia/Brisbane', + }; + // TODO: simplify SVG + return ( + + onClickContinent(e, timezones.northAmerica)} + > + + + + + + + + + + + onClickContinent(e, timezones.southAmerica)} + > + + + onClickContinent(e, timezones.europe)} + > + + + + + + + + + + + + + onClickContinent(e, timezones.asia)} + > + + + + + + + + + + + + + + + + + + + + onClickContinent(e, timezones.africa)} + > + + + + onClickContinent(e, timezones.australia)} + > + + + + + + ); +} diff --git a/docs/src/modules/components/overview/XLogo.tsx b/docs/src/modules/components/overview/XLogo.tsx new file mode 100644 index 000000000000..472fb773d5bb --- /dev/null +++ b/docs/src/modules/components/overview/XLogo.tsx @@ -0,0 +1,29 @@ +import * as React from 'react'; +import Typography from '@mui/material/Typography'; +// @ts-ignore +import IconImage from 'docs/src/components/icon/IconImage'; + +export default function XLogo() { + return ( + ({ + color: 'primary.600', + display: 'flex', + alignItems: 'center', + justifyContent: { xs: 'center', md: 'flex-start' }, + '& > *': { mr: 1 }, + ...theme.applyDarkStyles({ + color: 'primary.400', + }), + }), + ]} + > + MUI X + + ); +} diff --git a/docs/src/modules/components/overview/mainDemo/Birthday.tsx b/docs/src/modules/components/overview/mainDemo/Birthday.tsx new file mode 100644 index 000000000000..80d58a8139fe --- /dev/null +++ b/docs/src/modules/components/overview/mainDemo/Birthday.tsx @@ -0,0 +1,34 @@ +import * as React from 'react'; +import dayjs from 'dayjs'; +import Button from '@mui/material/Button'; +import Card from '@mui/material/Card'; +import Stack from '@mui/material/Stack'; +import InputAdornment from '@mui/material/InputAdornment'; +import CakeIcon from '@mui/icons-material/Cake'; +import { DateField } from '@mui/x-date-pickers/DateField'; + +export default function Birthday() { + return ( + + + + + + ), + }} + /> + + + + ); +} diff --git a/docs/src/modules/components/overview/mainDemo/Clock.tsx b/docs/src/modules/components/overview/mainDemo/Clock.tsx new file mode 100644 index 000000000000..2b1b1d5eb80b --- /dev/null +++ b/docs/src/modules/components/overview/mainDemo/Clock.tsx @@ -0,0 +1,52 @@ +import * as React from 'react'; +import dayjs, { Dayjs } from 'dayjs'; +import { styled } from '@mui/material/styles'; +import Card from '@mui/material/Card'; +import { StaticTimePicker } from '@mui/x-date-pickers/StaticTimePicker'; +import { + PickersLayoutProps, + usePickerLayout, + pickersLayoutClasses, + PickersLayoutRoot, + PickersLayoutContentWrapper, +} from '@mui/x-date-pickers/PickersLayout'; +import { TimeView } from '@mui/x-date-pickers/models'; + +const StyledLayout = styled(PickersLayoutRoot)({ + overflow: 'auto', + minWidth: 'fit-content', + [`.${pickersLayoutClasses.toolbar}`]: { + padding: '4px 16px', + }, + [`.${pickersLayoutClasses.contentWrapper}`]: { + '& .MuiTimeClock-root': { + width: 'fit-content', + }, + '& .MuiPickersArrowSwitcher-root': { + justifyContent: 'space-between', + width: '100%', + right: 0, + top: '2px', + }, + }, +}); + +function CustomLayout(props: PickersLayoutProps) { + const { actionBar, content, toolbar } = usePickerLayout(props); + return ( + + {toolbar} + + {content} + {actionBar} + + + ); +} +export default function Clock() { + return ( + + + + ); +} diff --git a/docs/src/modules/components/overview/mainDemo/DateRangeWithShortcuts.tsx b/docs/src/modules/components/overview/mainDemo/DateRangeWithShortcuts.tsx new file mode 100644 index 000000000000..a197487ce22d --- /dev/null +++ b/docs/src/modules/components/overview/mainDemo/DateRangeWithShortcuts.tsx @@ -0,0 +1,114 @@ +import * as React from 'react'; +import dayjs, { Dayjs } from 'dayjs'; +import { useTheme } from '@mui/material/styles'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import Card from '@mui/material/Card'; +import { StaticDateRangePicker } from '@mui/x-date-pickers-pro/StaticDateRangePicker'; +import { PickersShortcutsItem } from '@mui/x-date-pickers/PickersShortcuts'; +import { + PickersLayoutProps, + usePickerLayout, + pickersLayoutClasses, + PickersLayoutRoot, + PickersLayoutContentWrapper, +} from '@mui/x-date-pickers/PickersLayout'; +import { DateRange } from '@mui/x-date-pickers-pro/models'; + +const shortcutsItems: PickersShortcutsItem>[] = [ + { + label: 'This Week', + getValue: () => { + const today = dayjs(); + return [today.startOf('week'), today.endOf('week')]; + }, + }, + { + label: 'Last Week', + getValue: () => { + const today = dayjs(); + const prevWeek = today.subtract(7, 'day'); + return [prevWeek.startOf('week'), prevWeek.endOf('week')]; + }, + }, + { + label: 'Last 7 Days', + getValue: () => { + const today = dayjs(); + return [today.subtract(7, 'day'), today]; + }, + }, + { + label: 'Current Month', + getValue: () => { + const today = dayjs(); + return [today.startOf('month'), today.endOf('month')]; + }, + }, + { + label: 'Next Month', + getValue: () => { + const today = dayjs(); + const startOfNextMonth = today.endOf('month').add(1, 'day'); + return [startOfNextMonth, startOfNextMonth.endOf('month')]; + }, + }, + { label: 'Reset', getValue: () => [null, null] }, +]; + +interface CustomLayoutProps extends PickersLayoutProps, Dayjs, 'day'> { + isHorizontal?: boolean; +} +function CustomLayout(props: CustomLayoutProps) { + const { isHorizontal, ...other } = props; + const { tabs, content, shortcuts } = usePickerLayout(other); + + return ( + + {shortcuts} + + {tabs} + {content} + + + ); +} + +export default function DateRangeWithShortcuts() { + const theme = useTheme(); + const showTwoCalendars = useMediaQuery('(min-width:700px)'); + const lgDown = useMediaQuery(theme.breakpoints.down('lg')); + const smUp = useMediaQuery(theme.breakpoints.up('sm')); + const xlDown = useMediaQuery(theme.breakpoints.down('xl')); + return ( + + + + ); +} diff --git a/docs/src/modules/components/overview/mainDemo/DigitalClock.tsx b/docs/src/modules/components/overview/mainDemo/DigitalClock.tsx new file mode 100644 index 000000000000..a73e74007fca --- /dev/null +++ b/docs/src/modules/components/overview/mainDemo/DigitalClock.tsx @@ -0,0 +1,67 @@ +import * as React from 'react'; +import { Dayjs } from 'dayjs'; +import { styled } from '@mui/material/styles'; +import Card from '@mui/material/Card'; +import Paper from '@mui/material/Paper'; +import Typography from '@mui/material/Typography'; +import { StaticTimePicker } from '@mui/x-date-pickers/StaticTimePicker'; +import { + PickersLayoutProps, + usePickerLayout, + pickersLayoutClasses, + PickersLayoutRoot, + PickersLayoutContentWrapper, +} from '@mui/x-date-pickers/PickersLayout'; +import { renderMultiSectionDigitalClockTimeView } from '@mui/x-date-pickers/timeViewRenderers'; +import { TimeView } from '@mui/x-date-pickers/models'; +import { TimePickerViewRenderers } from '@mui/x-date-pickers/TimePicker/shared'; + +const StyledLayout = styled(PickersLayoutRoot)({ + overflow: 'auto', + [`.${pickersLayoutClasses.contentWrapper}`]: { + '& .MuiClock-root': { + width: 'fit-content', + }, + }, +}); + +function CustomLayout(props: PickersLayoutProps) { + const { actionBar, content } = usePickerLayout(props); + return ( + + + {content} + {actionBar} + + + ); +} +export default function DigitalClock() { + return ( + + + Book now! + + `1px solid ${theme.palette.divider}`, + flexWrap: 'wrap', + padding: 0, + }} + > + + } + /> + + + ); +} diff --git a/docs/src/modules/components/overview/mainDemo/FlightPicker.tsx b/docs/src/modules/components/overview/mainDemo/FlightPicker.tsx new file mode 100644 index 000000000000..d75649a27537 --- /dev/null +++ b/docs/src/modules/components/overview/mainDemo/FlightPicker.tsx @@ -0,0 +1,31 @@ +import * as React from 'react'; +import Card from '@mui/material/Card'; +import Typography from '@mui/material/Typography'; +import InputAdornment from '@mui/material/InputAdornment'; +import FlightLandIcon from '@mui/icons-material/FlightLand'; +import FlightTakeoffIcon from '@mui/icons-material/FlightTakeoff'; +import { DateRangePicker } from '@mui/x-date-pickers-pro/DateRangePicker'; + +export default function FlightPicker() { + return ( + + + Book your flight + + ({ + label: position === 'start' ? 'Outbound' : 'Inbound', + InputProps: { + startAdornment: ( + + {position === 'start' ? : } + + ), + }, + }), + }} + /> + + ); +} diff --git a/docs/src/modules/components/overview/mainDemo/PickerButton.tsx b/docs/src/modules/components/overview/mainDemo/PickerButton.tsx new file mode 100644 index 000000000000..0dc82b364e0f --- /dev/null +++ b/docs/src/modules/components/overview/mainDemo/PickerButton.tsx @@ -0,0 +1,71 @@ +import * as React from 'react'; +import dayjs, { Dayjs } from 'dayjs'; +import Button from '@mui/material/Button'; +import Card from '@mui/material/Card'; +import CalendarTodayRoundedIcon from '@mui/icons-material/CalendarTodayRounded'; +import { UseDateFieldProps } from '@mui/x-date-pickers/DateField'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; +import { + BaseSingleInputFieldProps, + DateValidationError, + FieldSection, +} from '@mui/x-date-pickers/models'; + +interface ButtonFieldProps + extends UseDateFieldProps, + BaseSingleInputFieldProps { + setOpen?: React.Dispatch>; +} + +function ButtonField(props: ButtonFieldProps) { + const { + setOpen, + label, + id, + disabled, + InputProps: { ref } = {}, + inputProps: { 'aria-label': ariaLabel } = {}, + } = props; + + return ( + + ); +} + +export default function PickerButton() { + const [value, setValue] = React.useState(dayjs('2023-04-17')); + const [open, setOpen] = React.useState(false); + + return ( + + setValue(newValue)} + slots={{ field: ButtonField }} + slotProps={{ + field: { setOpen } as any, + nextIconButton: { size: 'small' }, + previousIconButton: { size: 'small' }, + }} + open={open} + onClose={() => setOpen(false)} + onOpen={() => setOpen(true)} + views={['day', 'month', 'year']} + /> + + ); +} diff --git a/docs/src/modules/components/overview/mainDemo/ThemeToggleGroup.tsx b/docs/src/modules/components/overview/mainDemo/ThemeToggleGroup.tsx new file mode 100644 index 000000000000..5483088efd98 --- /dev/null +++ b/docs/src/modules/components/overview/mainDemo/ThemeToggleGroup.tsx @@ -0,0 +1,63 @@ +import * as React from 'react'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import { styled, useTheme } from '@mui/material/styles'; +import ToggleButton from '@mui/material/ToggleButton'; +import ToggleButtonGroup, { toggleButtonGroupClasses } from '@mui/material/ToggleButtonGroup'; +import Paper from '@mui/material/Paper'; +import SettingsSuggestIcon from '@mui/icons-material/SettingsSuggest'; +import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh'; + +const StyledToggleButtonGroup = styled(ToggleButtonGroup)(({ theme }) => ({ + flexGrow: 1, + gap: theme.spacing(1), + padding: theme.spacing(0.8), + [`& .${toggleButtonGroupClasses.grouped}`]: { + border: 0, + borderRadius: theme.shape.borderRadius, + }, +})); + +export type ThemeToggleGroupProps = { + showCustomTheme: boolean; + toggleCustomTheme: () => void; +}; + +export default function ThemeToggleGroup({ + showCustomTheme, + toggleCustomTheme, +}: ThemeToggleGroupProps) { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('sm')); + + return ( + + { + if (newValue !== null) { + toggleCustomTheme(); + } + }} + exclusive + size="small" + > + + + {isMobile && 'Custom Theme'} + + + + {isMobile && 'Default Theme'} + + + + ); +} diff --git a/packages/x-date-pickers/src/PickersTextField/PickersTextField.tsx b/packages/x-date-pickers/src/PickersTextField/PickersTextField.tsx index 0303989d847c..1ba9e2b3f1d1 100644 --- a/packages/x-date-pickers/src/PickersTextField/PickersTextField.tsx +++ b/packages/x-date-pickers/src/PickersTextField/PickersTextField.tsx @@ -139,6 +139,7 @@ const PickersTextField = React.forwardRef(function PickersTextField( areAllSectionsEmpty={areAllSectionsEmpty} onClick={onClick} onKeyDown={onKeyDown} + onKeyUp={onKeyUp} onInput={onInput} onPaste={onPaste} endAdornment={endAdornment}