From 3cd58d67b239a31292af4b5ee02874d6e1e1bebf Mon Sep 17 00:00:00 2001 From: Dmitriy Kovalenko Date: Sun, 1 Mar 2020 20:47:12 +0200 Subject: [PATCH] LocalizationProvider (#1537) * Rename MuiPickersUtilsPorvider => LocalizationProvider * Improve utils typings * Implement global format override * [docs] Change name of date-io customization page * Update examples to include new wording * Fix prop-types typescript error * Add daetAdapter prop for passing date-io utils directly to component * Update percy and cypress * Unskip flaky test * Fix typo in error message * Fix cypress test * One more try to fix flaky test * Remove flaky test * Fix visual regression scenarios tests with new version of cypress * Run cypress tests in chrome * Ignore dark theme change in snapshots * Try to fix example styles one more time * Fix inccorect name of cypress executor * Try weird hack to reinject styles * Optimize theme toggling for visual regression * Update scenario names to get rid of duplications * Rename `adapter` => `dateAdapter` * Rename libFormats => dateFormats, libInstance => dateLibInstance * Remove version-specific code from README.md * Fix documentation erros in installation guide * Run prettier on README.md * Use edge="end" for keyboard adornment icons, closes #1545 --- .circleci/config.yml | 4 +- .percy.yml | 1 - README.md | 31 +- docs/layout/PageWithContext.tsx | 6 +- docs/layout/components/navigationMap.ts | 3 +- docs/pages/getting-started/installation.mdx | 25 +- docs/pages/getting-started/usage.mdx | 11 +- docs/pages/guides/DateAdapterProp.example.jsx | 35 ++ docs/pages/guides/Formats.example.jsx | 18 +- docs/pages/guides/OverrideLogic.example.jsx | 24 ++ docs/pages/guides/date-adapter-passing.mdx | 26 ++ docs/pages/guides/date-io-customization.mdx | 38 ++ docs/pages/guides/formats.mdx | 30 -- docs/pages/guides/upgrading-to-v3.mdx | 2 +- .../Date-fns-customized.example.jsx | 106 ----- docs/pages/localization/Date-fns.example.jsx | 6 +- docs/pages/localization/Hijri.example.jsx | 13 +- docs/pages/localization/Moment.example.jsx | 6 +- docs/pages/localization/Persian.example.jsx | 13 +- docs/pages/localization/date-fns.mdx | 9 +- docs/prop-types.json | 45 ++- docs/utils/utilsService.ts | 16 +- e2e/integration/DatePicker.spec.ts | 18 - e2e/integration/KeyboardNavigation.spec.ts | 4 +- e2e/integration/VisualRegression.spec.ts | 75 +++- e2e/plugins/index.js | 2 + lib/.size-snapshot.json | 22 +- .../DateTimePicker/DateTimePickerToolbar.tsx | 1 + lib/src/LocalizationProvider.tsx | 41 ++ lib/src/MuiPickersUtilsProvider.tsx | 39 -- lib/src/Picker/makePickerWithState.tsx | 6 +- lib/src/TimePicker/TimePicker.tsx | 4 +- .../MuiPickersUtilsProvider.test.tsx | 10 +- lib/src/__tests__/e2e/DateTimePicker.test.tsx | 23 ++ lib/src/__tests__/test-utils.tsx | 6 +- lib/src/_helpers/text-field-helper.ts | 14 +- lib/src/_helpers/time-utils.ts | 6 +- lib/src/_shared/KeyboardDateInput.tsx | 1 + lib/src/_shared/PureDateInput.tsx | 4 +- lib/src/_shared/hooks/useUtils.ts | 16 +- lib/src/_shared/withDateAdapterProp.tsx | 28 ++ lib/src/index.ts | 5 +- lib/src/views/Clock/Clock.tsx | 3 +- lib/src/views/Clock/ClockView.tsx | 3 +- package.json | 4 +- yarn.lock | 378 ++++++------------ 46 files changed, 559 insertions(+), 622 deletions(-) create mode 100644 docs/pages/guides/DateAdapterProp.example.jsx create mode 100644 docs/pages/guides/OverrideLogic.example.jsx create mode 100644 docs/pages/guides/date-adapter-passing.mdx create mode 100644 docs/pages/guides/date-io-customization.mdx delete mode 100644 docs/pages/guides/formats.mdx delete mode 100644 docs/pages/localization/Date-fns-customized.example.jsx create mode 100644 lib/src/LocalizationProvider.tsx delete mode 100644 lib/src/MuiPickersUtilsProvider.tsx create mode 100644 lib/src/_shared/withDateAdapterProp.tsx diff --git a/.circleci/config.yml b/.circleci/config.yml index 7f5994797..3bf288470 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -89,7 +89,7 @@ jobs: cypress_tests: description: Run cypress tests - executor: cypress/browsers-chrome73-ff68 + executor: cypress/browsers-chrome78-ff70 steps: - attach_workspace: at: . @@ -111,7 +111,7 @@ jobs: environment: VISUAL_TESTING: true - run: npx wait-on http://localhost:3000 - - run: yarn percy exec -- cypress run --record + - run: yarn percy exec -- cypress run --record --browser chrome #################### # Workflow diff --git a/.percy.yml b/.percy.yml index 0b65c6acd..01e64f6cb 100644 --- a/.percy.yml +++ b/.percy.yml @@ -4,4 +4,3 @@ snapshot: #codefund { display: none; } - | diff --git a/README.md b/README.md index 432d46ee0..ad8c5ae90 100644 --- a/README.md +++ b/README.md @@ -31,36 +31,9 @@ npm i @material-ui/pickers yarn add @material-ui/pickers ``` -Now choose the library that pickers will use to work with date. We are providing interfaces for [moment](https://momentjs.com/), [luxon](https://moment.github.io/luxon/), [dayjs](https://github.com/iamkun/dayjs) and [date-fns v2](https://date-fns.org/). If you are not using moment in the project (or don’t have it in the bundle already) we suggest using date-fns or luxon, because they are much lighter and will be correctly tree-shaked from the bundle. Note, that for v3.x version of @material-ui/pickers use v1.x version of [date-io](https://github.com/dmtrKovalenko/date-io). +### Getting started -```sh -npm i date-fns@next @date-io/date-fns@1.x -// or -npm i moment @date-io/moment@1.x -// or -npm i luxon @date-io/luxon@1.x -// or -npm i dayjs @date-io/dayjs@1.x -``` - -Then teach pickers which library to use with `MuiPickerUtilsProvider`. This component takes a utils property, and makes it available down the React tree thanks to React context. It should preferably be used at the root of your component tree. - -```jsx -import MomentUtils from '@date-io/moment'; -import DateFnsUtils from '@date-io/date-fns'; -import LuxonUtils from '@date-io/luxon'; -import { MuiPickersUtilsProvider } from '@material-ui/pickers'; - -function App() { - return ( - - - - ); -} - -render(, document.querySelector('#app')); -``` +[Here is instruction](https://material-ui-pickers.dev/getting-started/installation#peer-library) of how to get started with `@material-ui/pickers`. ## Documentation diff --git a/docs/layout/PageWithContext.tsx b/docs/layout/PageWithContext.tsx index 7a09ebf5d..4a1e8d81f 100644 --- a/docs/layout/PageWithContext.tsx +++ b/docs/layout/PageWithContext.tsx @@ -6,8 +6,8 @@ import { create } from 'jss'; import { SnackbarProvider } from 'notistack'; import { setPrismTheme } from '../utils/prism'; import { PageContext } from '../utils/getPageContext'; +import { LocalizationProvider } from '@material-ui/pickers'; import { UtilsContext } from '../_shared/UtilsServiceContext'; -import { MuiPickersUtilsProvider } from '@material-ui/pickers'; import { NotificationManager } from 'utils/NotificationManager'; import { Theme, createMuiTheme, CssBaseline } from '@material-ui/core'; import { createUtilsService, UtilsLib, utilsMap } from '../utils/utilsService'; @@ -99,7 +99,7 @@ export const PageWithContexts: React.SFC = ({ > - + @@ -113,7 +113,7 @@ export const PageWithContexts: React.SFC = ({ /> - + diff --git a/docs/layout/components/navigationMap.ts b/docs/layout/components/navigationMap.ts index eec4732d9..3e4121d08 100644 --- a/docs/layout/components/navigationMap.ts +++ b/docs/layout/components/navigationMap.ts @@ -41,7 +41,8 @@ export const navItems = [ { title: 'Accessibility', href: '/guides/accessibility' }, { title: 'Form integration', href: '/guides/form-integration' }, { title: 'CSS overrides', href: '/guides/css-overrides' }, - { title: 'Global format customization', href: '/guides/formats' }, + { title: 'Passing date adapter', href: '/guides/date-adapter-passing' }, + { title: 'Date management customization', href: '/guides/date-io-customization' }, { title: 'Open pickers programmatically', href: '/guides/controlling-programmatically', diff --git a/docs/pages/getting-started/installation.mdx b/docs/pages/getting-started/installation.mdx index b3e05d7b6..5e841e1bb 100644 --- a/docs/pages/getting-started/installation.mdx +++ b/docs/pages/getting-started/installation.mdx @@ -20,37 +20,38 @@ yarn add @material-ui/pickers @material-ui/pickers was designed to use the date management library of your choice. We are providing interfaces for [moment](https://momentjs.com/), [date-fns 2](https://date-fns.org/), [luxon](https://moment.github.io/luxon/) and [dayjs](https://github.com/iamkun/dayjs). -**Important:** we only support date-fns versions `v2` upwards. - ``` -npm i @date-io/date-fns date-fns@next +npm i date-fns // or -npm i @date-io/moment moment +npm i moment // or -npm i -s @date-io/luxon luxon +npm i luxon // or -npm i -s @date-io/dayjs dayjs +npm i dayjs ``` -Tell pickers which date management library it should use with `MuiPickersUtilsProvider`. This component takes a utils -`prop`, and makes it available down the React tree with [React Context](https://reactjs.org/docs/context.html). It should +Tell pickers which date management library it should use with `LocalizationProvider`. This component takes a `dateAdapter` +prop, and makes it available down the React tree thanks to [React Context](https://reactjs.org/docs/context.html). It should be used at the root of your component tree, or at the highest level you wish the pickers to be available. ```jsx -import { MuiPickersUtilsProvider } from '@material-ui/pickers'; +import { LocalizationProvider } from '@material-ui/pickers'; -// pick a date util library +// pick an adapter for your date library import MomentUtils from '@material-ui-pickers/adapter/moment'; import DateFnsUtils from '@material-ui-pickers/adapter/date-fns'; import LuxonUtils from '@material-ui-pickers/adapter/luxon'; function App() { return ( - + - + ); } ReactDOM.render(, document.querySelector('#app')); ``` + +Also it is possible to pass [date adapter directly to picker](/guides/date-adapter-passing) and avoid using context, +but please use this possibility widely. diff --git a/docs/pages/getting-started/usage.mdx b/docs/pages/getting-started/usage.mdx index 64b11ad3b..432b15d6d 100644 --- a/docs/pages/getting-started/usage.mdx +++ b/docs/pages/getting-started/usage.mdx @@ -18,22 +18,17 @@ import { CODESANDBOX_EXAMPLE_ID } from '_constants'; ```jsx import React, { useState } from 'react'; import DateFnsUtils from '@material-ui-pickers/adapter/date-fns'; // choose your lib -import { - DatePicker, - TimePicker, - DateTimePicker, - MuiPickersUtilsProvider, -} from '@material-ui/pickers'; +import { DatePicker, TimePicker, DateTimePicker, LocalizationProvider } from '@material-ui/pickers'; function App() { const [selectedDate, handleDateChange] = useState(new Date()); return ( - + handleDateChange(date)} /> handleDateChange(date)} /> handleDateChange(date)} /> - + ); } ``` diff --git a/docs/pages/guides/DateAdapterProp.example.jsx b/docs/pages/guides/DateAdapterProp.example.jsx new file mode 100644 index 000000000..fccfb5a9e --- /dev/null +++ b/docs/pages/guides/DateAdapterProp.example.jsx @@ -0,0 +1,35 @@ +import React, { useState } from 'react'; +import ruLocale from 'date-fns/locale/ru'; +import deLocale from 'date-fns/locale/de'; +import DateFnsAdapter from '@material-ui/pickers/adapter/date-fns'; +import { useMemo } from 'react'; +import { DatePicker } from '@material-ui/pickers'; + +const staticDateAdapter = new DateFnsAdapter({ locale: ruLocale }); + +function UsingDateAdapterProp() { + const [locale] = useState(deLocale); + const [selectedDate, handleDateChange] = useState(new Date()); + const memoizedDateAdapter = useMemo(() => { + return new DateFnsAdapter({ locale }); + }, [locale]); + + return ( + <> + handleDateChange(date)} + dateAdapter={staticDateAdapter} + /> + + handleDateChange(date)} + dateAdapter={memoizedDateAdapter} + /> + + ); +} + +export default UsingDateAdapterProp; diff --git a/docs/pages/guides/Formats.example.jsx b/docs/pages/guides/Formats.example.jsx index 0198571fe..82a162531 100644 --- a/docs/pages/guides/Formats.example.jsx +++ b/docs/pages/guides/Formats.example.jsx @@ -1,31 +1,29 @@ -import format from 'date-fns/format'; import React, { useState } from 'react'; import frLocale from 'date-fns/locale/fr'; import DateFnsAdapter from '@material-ui/pickers/adapter/date-fns'; import { DatePicker } from '@material-ui/pickers'; -import { MuiPickersUtilsProvider } from '@material-ui/pickers'; +import { LocalizationProvider } from '@material-ui/pickers'; -class LocalizedUtils extends DateFnsAdapter { - getDatePickerHeaderText(date) { - return format(date, 'd MMM yyyy', { locale: this.locale }); - } -} +/** @type Partial */ +const formats = { + normalDate: 'd MMM yyy', + keyboardDate: 'd MMM yyy', +}; function DateFnsLocalizationExample() { const [selectedDate, handleDateChange] = useState(new Date()); return ( - + - + ); } diff --git a/docs/pages/guides/OverrideLogic.example.jsx b/docs/pages/guides/OverrideLogic.example.jsx new file mode 100644 index 000000000..1044a75ed --- /dev/null +++ b/docs/pages/guides/OverrideLogic.example.jsx @@ -0,0 +1,24 @@ +import React, { useState } from 'react'; +import DateFnsAdapter from '@material-ui/pickers/adapter/date-fns'; +import { DatePicker } from '@material-ui/pickers'; +import { LocalizationProvider } from '@material-ui/pickers'; + +// Simple example, in some cases it can be helpful to reverse year order (from future to past). +// Here we are simply override called by pickers function and reversing the result +class OverriddenAdapter extends DateFnsAdapter { + getYearRange(start, end) { + return super.getYearRange(start, end).reverse(); + } +} + +function DateFnsLocalizationExample() { + const [selectedDate, handleDateChange] = useState(new Date()); + + return ( + + + + ); +} + +export default DateFnsLocalizationExample; diff --git a/docs/pages/guides/date-adapter-passing.mdx b/docs/pages/guides/date-adapter-passing.mdx new file mode 100644 index 000000000..63f7338f8 --- /dev/null +++ b/docs/pages/guides/date-adapter-passing.mdx @@ -0,0 +1,26 @@ +import Ad from '_shared/Ad'; +import Code from '_shared/Code.tsx'; +import Example from '_shared/Example'; +import PageMeta from '_shared/PageMeta'; +import * as DateAdapterProp from './DateAdapterProp.example'; + + + +## Passing date adapter down to pickers + +There are a couple of ways to pass date-io adapter to pickers components. + + + +#### Context vs dateAdapter prop + +Recomended way to pass date adapter is using our `LocalizationProvider` and pass it through the context. +Also there is a `dateAdapter` prop available, it allows to get rid of additional context. + +But you need to understand a few things: + +- `dateAdapter` will create a new context instance inside the date-picker, it may impact rendering performance if you have a lot of pickers on the screen +- You must make sure your adapter is properly memoized before passing it down + (if not – all components tree inside any picker will always rerender on your component change) + + diff --git a/docs/pages/guides/date-io-customization.mdx b/docs/pages/guides/date-io-customization.mdx new file mode 100644 index 000000000..624e49bc6 --- /dev/null +++ b/docs/pages/guides/date-io-customization.mdx @@ -0,0 +1,38 @@ +import Ad from '_shared/Ad'; +import Code from '_shared/Code.tsx'; +import Example from '_shared/Example'; +import PageMeta from '_shared/PageMeta'; +import utilsInterfaceCode from '!raw-loader!@date-io/core/IUtils.d.ts'; +import * as FormatsExample from './Formats.example'; +import * as OverrideLogicExample from './OverrideLogic.example'; + + + +## Customization date management logic + +For some reason, like timezone management and localization you may need to control how datepickers are working +with your date management library. + + + +#### Global formats customization + +All the formats used by the datepicker can be changed by `libFormats` prop in `LocalizationProvider`. +Find all availble formats in [Adapter interface](#utils-interface) + + + +#### Override date logic + +It is also possible to extend any adapter we providing and change the logic of date manipulations. +Simply extend exported version of `date-io` adapter, and make it work as you need accordingly to [used interface](#utils-interface) + +> You can use ES6 class syntax or override values with the help of `.prototype` Object property. + + + +#### Utils interface + +_Note_ TDate - date object passed from the state (moment, native Date or Luxon`s DateTime) + + diff --git a/docs/pages/guides/formats.mdx b/docs/pages/guides/formats.mdx deleted file mode 100644 index c6881039b..000000000 --- a/docs/pages/guides/formats.mdx +++ /dev/null @@ -1,30 +0,0 @@ -import Ad from '_shared/Ad'; -import Code from '_shared/Code.tsx'; -import Example from '_shared/Example'; -import PageMeta from '_shared/PageMeta'; -import utilsInterfaceCode from '!raw-loader!@date-io/core/IUtils.d.ts'; -import * as FormatsExample from './Formats.example'; - - - -## Format customization - -For localization purposes, it may be needed to change display values in the pickers' modals, -because the default formats cannot be idiomatic for some localizations. Utils can help you there. - - - -It's possible to override any of the displayed date values by inheritance of utils passed to -MuiPickersProvider. - -#### Patched French pickers - -You can use ES6 class syntax or override values with the help of `.prototype` Object property. - - - -#### Utils interface - -_Note_ TDate - date object passed from state (moment, native Date or Luxon`s DateTime) - - diff --git a/docs/pages/guides/upgrading-to-v3.mdx b/docs/pages/guides/upgrading-to-v3.mdx index fb2c3ce9f..1e81cae19 100644 --- a/docs/pages/guides/upgrading-to-v3.mdx +++ b/docs/pages/guides/upgrading-to-v3.mdx @@ -128,7 +128,7 @@ So less `any` from our side :) #### 🙈 Misc - Improved accessibility by making the toolbar button actually focusable `