diff --git a/packages/x-date-pickers/src/CalendarPicker/CalendarPicker.test.tsx b/packages/x-date-pickers/src/CalendarPicker/CalendarPicker.test.tsx index c232bd22977da..c8ae0bb16a547 100644 --- a/packages/x-date-pickers/src/CalendarPicker/CalendarPicker.test.tsx +++ b/packages/x-date-pickers/src/CalendarPicker/CalendarPicker.test.tsx @@ -14,6 +14,8 @@ import { createPickerRenderer, } from '../../../../test/utils/pickers-utils'; +const isJSDOM = /jsdom/.test(window.navigator.userAgent); + describe('', () => { const { render, clock } = createPickerRenderer({ clock: 'fake' }); @@ -412,5 +414,33 @@ describe('', () => { expect(onChange.callCount).to.equal(0); expect(screen.getByMuiTest('calendar-month-and-year-text')).to.have.text('January 2022'); }); + + it('should scroll to show the selected year', function test() { + if (isJSDOM) { + this.skip(); // Needs layout + } + render( + {}} + views={['year']} + openTo="year" + />, + ); + + // const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; + // + const rootElement = document.querySelector('.MuiCalendarPicker-root')!; + const selectedButton = document.querySelector('.Mui-selected')!; + + expect(rootElement).not.to.equal(null); + expect(selectedButton).not.to.equal(null); + + const parentBoundingBox = rootElement.getBoundingClientRect(); + const buttonBoundingBox = selectedButton.getBoundingClientRect(); + + expect(parentBoundingBox.top).not.to.greaterThan(buttonBoundingBox.top); + expect(parentBoundingBox.bottom).not.to.lessThan(buttonBoundingBox.bottom); + }); }); }); diff --git a/packages/x-date-pickers/src/CalendarPicker/CalendarPicker.tsx b/packages/x-date-pickers/src/CalendarPicker/CalendarPicker.tsx index 939d504b20dca..002edc9a9c54d 100644 --- a/packages/x-date-pickers/src/CalendarPicker/CalendarPicker.tsx +++ b/packages/x-date-pickers/src/CalendarPicker/CalendarPicker.tsx @@ -204,9 +204,7 @@ const CalendarPickerViewTransitionContainer = styled(PickersFadeTransitionGroup, name: 'MuiCalendarPicker', slot: 'ViewTransitionContainer', overridesResolver: (props, styles) => styles.viewTransitionContainer, -})<{ ownerState: CalendarPickerProps }>({ - overflowY: 'auto', -}); +})<{ ownerState: CalendarPickerProps }>({}); type CalendarPickerComponent = (( props: CalendarPickerProps & React.RefAttributes, @@ -462,6 +460,11 @@ export const CalendarPicker = React.forwardRef(function CalendarPicker( }, ); + React.useEffect(() => { + // Set focus to the button when switching from a view to another + handleFocusedViewChange(openView)(true); + }, [openView, handleFocusedViewChange]); + return ( { const classes = useUtilityClasses(ownerState); - // TODO: Can we just forward this to the button? + // We can't forward the `autoFocus` to the button because it is a native button, not a MUI Button React.useEffect(() => { if (autoFocus) { // `ref.current` being `null` would be a bug in MUI. diff --git a/packages/x-date-pickers/src/YearPicker/YearPicker.tsx b/packages/x-date-pickers/src/YearPicker/YearPicker.tsx index eda57d29453b2..f47c32d50b65f 100644 --- a/packages/x-date-pickers/src/YearPicker/YearPicker.tsx +++ b/packages/x-date-pickers/src/YearPicker/YearPicker.tsx @@ -4,7 +4,7 @@ import clsx from 'clsx'; import { SxProps, useTheme } from '@mui/system'; import { styled, useThemeProps, Theme } from '@mui/material/styles'; import { unstable_composeClasses as composeClasses } from '@mui/material'; -import '@mui/material/utils'; +import { useForkRef } from '@mui/material/utils'; import { unstable_useControlled as useControlled, unstable_useEventCallback as useEventCallback, @@ -60,7 +60,8 @@ const YearPickerRoot = styled('div', { flexWrap: 'wrap', overflowY: 'auto', height: '100%', - margin: '0 4px', + padding: '0 4px', + maxHeight: '304px', }); export interface YearPickerProps @@ -149,6 +150,7 @@ export const YearPicker = React.forwardRef(function YearPicker( return utils.getYear(now); }, [now, value, utils, disableHighlightToday]); + const [focusedYear, setFocusedYear] = React.useState(() => selectedYear || todayYear); const [internalHasFocus, setInternalHasFocus] = useControlled({ @@ -243,9 +245,37 @@ export const YearPicker = React.forwardRef(function YearPicker( } }); + const scrollerRef = React.useRef(null); + const handleRef = useForkRef(ref, scrollerRef); + React.useEffect(() => { + if (autoFocus || scrollerRef.current === null) { + return; + } + const tabbableButton = scrollerRef.current.querySelector('[tabindex="0"]'); + if (!tabbableButton) { + return; + } + + // Taken from useScroll in x-data-grid, but vertically centered + const offsetHeight = tabbableButton.offsetHeight; + const offsetTop = tabbableButton.offsetTop; + + const clientHeight = scrollerRef.current.clientHeight; + const scrollTop = scrollerRef.current.scrollTop; + + const elementBottom = offsetTop + offsetHeight; + + if (offsetHeight > clientHeight || offsetTop < scrollTop) { + // Button already visible + return; + } + + scrollerRef.current.scrollTop = elementBottom - clientHeight / 2 - offsetHeight / 2; + }, [autoFocus]); + return (