Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pickers] Unify PickersMonth and PickersYear behaviors #6034

Merged
merged 19 commits into from
Sep 9, 2022
Merged
22 changes: 9 additions & 13 deletions packages/x-date-pickers/src/MonthPicker/MonthPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,12 +174,12 @@ export const MonthPicker = React.forwardRef(function MonthPicker<TDate>(
[disableFuture, disablePast, maxDate, minDate, now, shouldDisableMonth, utils],
);

const onMonthSelect = (month: number) => {
const handleMonthSelection = (event: React.MouseEvent, year: number) => {
if (readOnly) {
return;
}

const newDate = utils.setMonth(selectedDateOrToday, month);
const newDate = utils.setMonth(selectedDateOrToday, year);
onChange(newDate, 'finish');
};

Expand Down Expand Up @@ -222,30 +222,26 @@ export const MonthPicker = React.forwardRef(function MonthPicker<TDate>(
);
}, [selectedMonth]);

const handleKeyDown = useEventCallback((event: React.KeyboardEvent) => {
const handleKeyDown = useEventCallback((event: React.KeyboardEvent, month: number) => {
flaviendelangle marked this conversation as resolved.
Show resolved Hide resolved
const monthsInYear = 12;
const monthsInRow = 3;

switch (event.key) {
case 'ArrowUp':
focusMonth((monthsInYear + focusedMonth - monthsInRow) % monthsInYear);
focusMonth((monthsInYear + month - monthsInRow) % monthsInYear);
event.preventDefault();
break;
case 'ArrowDown':
focusMonth((monthsInYear + focusedMonth + monthsInRow) % monthsInYear);
focusMonth((monthsInYear + month + monthsInRow) % monthsInYear);
event.preventDefault();
break;
case 'ArrowLeft':
focusMonth(
(monthsInYear + focusedMonth + (theme.direction === 'ltr' ? -1 : 1)) % monthsInYear,
);
focusMonth((monthsInYear + month + (theme.direction === 'ltr' ? -1 : 1)) % monthsInYear);

event.preventDefault();
break;
case 'ArrowRight':
focusMonth(
(monthsInYear + focusedMonth + (theme.direction === 'ltr' ? 1 : -1)) % monthsInYear,
);
focusMonth((monthsInYear + month + (theme.direction === 'ltr' ? 1 : -1)) % monthsInYear);

event.preventDefault();
break;
Expand All @@ -272,7 +268,6 @@ export const MonthPicker = React.forwardRef(function MonthPicker<TDate>(
ref={ref}
className={clsx(classes.root, className)}
ownerState={ownerState}
onKeyDown={handleKeyDown}
{...other}
>
{utils.getMonthArray(selectedDateOrToday).map((month) => {
Expand All @@ -287,7 +282,8 @@ export const MonthPicker = React.forwardRef(function MonthPicker<TDate>(
selected={monthNumber === selectedMonth}
tabIndex={monthNumber === focusedMonth && !isDisabled ? 0 : -1}
hasFocus={internalHasFocus && monthNumber === focusedMonth}
onSelect={onMonthSelect}
onClick={handleMonthSelection}
onKeyDown={handleKeyDown}
onFocus={handleMonthFocus}
onBlur={handleMonthBlur}
disabled={isDisabled}
Expand Down
133 changes: 92 additions & 41 deletions packages/x-date-pickers/src/MonthPicker/PickersMonth.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,44 @@
import * as React from 'react';
import clsx from 'clsx';
import Typography, { TypographyTypeMap } from '@mui/material/Typography';
import { styled, alpha } from '@mui/material/styles';
import { OverridableComponent } from '@mui/material/OverridableComponent';
import { generateUtilityClasses } from '@mui/material';
import { unstable_useEnhancedEffect as useEnhancedEffect } from '@mui/material/utils';
import { onSpaceOrEnter } from '../internals/utils/utils';
import {
generateUtilityClass,
generateUtilityClasses,
unstable_composeClasses as composeClasses,
} from '@mui/material';
import { capitalize, unstable_useEnhancedEffect as useEnhancedEffect } from '@mui/material/utils';
import {
WrapperVariant,
WrapperVariantContext,
} from '../internals/components/wrappers/WrapperVariantContext';

const classes = generateUtilityClasses('PrivatePickersMonth', ['root', 'selected']);
interface PickersMonthClasses {
root: string;
modeDesktop: string;
modeMobile: string;
monthButton: string;
disabled: string;
selected: string;
}

function getPickersYearUtilityClass(slot: string) {
return generateUtilityClass('PrivatePickersMonth', slot);
}

const pickersMonthClasses = generateUtilityClasses('PrivatePickersMonth', [
'root',
'modeMobile',
'modeDesktop',
'monthButton',
'disabled',
'selected',
]);

export interface MonthProps {
interface PickersMonthProps {
classes?: Partial<PickersMonthClasses>;
children: React.ReactNode;
disabled?: boolean;
onSelect: (value: number) => void;
onClick: (event: React.MouseEvent, value: number) => void;
onKeyDown: (event: React.KeyboardEvent, value: number) => void;
selected?: boolean;
value: number;
hasFocus: boolean;
Expand All @@ -21,60 +47,81 @@ export interface MonthProps {
tabIndex: number;
}

export type PickersMonthClassKey = keyof typeof classes;
interface PickersMonthOwnerState extends PickersMonthProps {
wrapperVariant: WrapperVariant;
}

const useUtilityClasses = (ownerState: PickersMonthOwnerState) => {
const { wrapperVariant, disabled, selected, classes } = ownerState;

const slots = {
root: ['root', wrapperVariant && `mode${capitalize(wrapperVariant)}`],
yearButton: ['yearButton', disabled && 'disabled', selected && 'selected'],
};

return composeClasses(slots, getPickersYearUtilityClass, classes);
};

const PickersMonthRoot = styled<
OverridableComponent<TypographyTypeMap<{ component?: React.ElementType; disabled?: boolean }>>
>(Typography)(({ theme }) => ({
flex: '1 0 33.33%',
const PickersYearRoot = styled('div')<{
flaviendelangle marked this conversation as resolved.
Show resolved Hide resolved
ownerState: PickersMonthOwnerState;
}>(({ ownerState }) => ({
flexBasis: '33.3%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
...(ownerState?.wrapperVariant === 'desktop' && {
flexBasis: '25%',
}),
flaviendelangle marked this conversation as resolved.
Show resolved Hide resolved
}));

const PickersYearButton = styled('button')<{
flaviendelangle marked this conversation as resolved.
Show resolved Hide resolved
ownerState: PickersMonthOwnerState;
}>(({ theme }) => ({
color: 'unset',
backgroundColor: 'transparent',
border: 0,
outline: 0,
...theme.typography.subtitle1,
margin: '8px 0',
height: 36,
width: 72,
borderRadius: 18,
cursor: 'pointer',
'&:focus, &:hover': {
backgroundColor: alpha(theme.palette.action.active, theme.palette.action.hoverOpacity),
},
'&:disabled': {
pointerEvents: 'none',
[`&.${pickersMonthClasses.disabled}`]: {
color: theme.palette.text.secondary,
},
[`&.${classes.selected}`]: {
[`&.${pickersMonthClasses.selected}`]: {
color: theme.palette.primary.contrastText,
backgroundColor: theme.palette.primary.main,
'&:focus, &:hover': {
backgroundColor: theme.palette.primary.dark,
},
},
})) as typeof Typography;
}));

const noop = () => {};
/**
* @ignore - do not document.
*/
export const PickersMonth: React.FC<MonthProps> = (props) => {
export const PickersMonth: React.FC<PickersMonthProps> = (props) => {
const {
disabled,
onSelect,
selected,
value,
tabIndex,
hasFocus,
onFocus = noop,
onBlur = noop,
onClick,
onKeyDown,
children,
...other
} = props;

const handleSelection = () => {
onSelect(value);
};
const wrapperVariant = React.useContext(WrapperVariantContext);

const ref = React.useRef<HTMLButtonElement>(null);
useEnhancedEffect(() => {
Expand All @@ -83,24 +130,28 @@ export const PickersMonth: React.FC<MonthProps> = (props) => {
}
}, [hasFocus]);

const ownerState = { ...props, wrapperVariant };

const classes = useUtilityClasses(ownerState);

return (
<PickersMonthRoot
ref={ref}
data-mui-test="month"
component="button"
type="button"
className={clsx(classes.root, {
[classes.selected]: selected,
})}
tabIndex={tabIndex}
onClick={handleSelection}
onKeyDown={onSpaceOrEnter(handleSelection)}
color={selected ? 'primary' : undefined}
variant={selected ? 'h5' : 'subtitle1'}
disabled={disabled}
onFocus={(event: React.FocusEvent) => onFocus(event, value)}
onBlur={(event: React.FocusEvent) => onBlur(event, value)}
{...other}
/>
<PickersYearRoot data-mui-test="month" className={classes.root} ownerState={ownerState}>
<PickersYearButton
ref={ref}
disabled={disabled}
type="button"
alexfauquette marked this conversation as resolved.
Show resolved Hide resolved
data-mui-test={`month-${value}`}
flaviendelangle marked this conversation as resolved.
Show resolved Hide resolved
tabIndex={disabled ? -1 : tabIndex}
onClick={(event) => onClick(event, value)}
onKeyDown={(event) => onKeyDown(event, value)}
onFocus={(event) => onFocus(event, value)}
onBlur={(event) => onBlur(event, value)}
className={classes.yearButton}
ownerState={ownerState}
{...other}
flaviendelangle marked this conversation as resolved.
Show resolved Hide resolved
>
{children}
</PickersYearButton>
</PickersYearRoot>
);
};
Loading