diff --git a/packages/react-core/src/components/CalendarMonth/CalendarMonth.tsx b/packages/react-core/src/components/CalendarMonth/CalendarMonth.tsx index c49cc6b715e..de4cb47d7f4 100644 --- a/packages/react-core/src/components/CalendarMonth/CalendarMonth.tsx +++ b/packages/react-core/src/components/CalendarMonth/CalendarMonth.tsx @@ -180,11 +180,8 @@ export const CalendarMonth = ({ useEffect(() => { // Calendar month should not be focused on page load - // Datepicker should place focus in calendar month when opened if ((shouldFocus || isDateFocused) && focusedDateValidated && focusRef.current) { focusRef.current.focus(); - } else { - setShouldFocus(true); } }, [focusedDate, isDateFocused, focusedDateValidated, focusRef]); diff --git a/packages/react-core/src/components/DatePicker/DatePicker.tsx b/packages/react-core/src/components/DatePicker/DatePicker.tsx index 4ab92e187dc..089b03eabb6 100644 --- a/packages/react-core/src/components/DatePicker/DatePicker.tsx +++ b/packages/react-core/src/components/DatePicker/DatePicker.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { css } from '@patternfly/react-styles'; import styles from '@patternfly/react-styles/css/components/DatePicker/date-picker'; import buttonStyles from '@patternfly/react-styles/css/components/Button/button'; +import calendarMonthStyles from '@patternfly/react-styles/css/components/CalendarMonth/calendar-month'; import { TextInput, TextInputProps } from '../TextInput/TextInput'; import { Popover, PopoverProps } from '../Popover/Popover'; import { InputGroup, InputGroupItem } from '../InputGroup'; @@ -214,9 +215,15 @@ const DatePickerBase = ( [setPopoverOpen, popoverOpen, selectOpen] ); + const createFocusSelectorString = (modifierClass: string) => + `.${calendarMonthStyles.calendarMonthDatesCell}.${modifierClass} .${calendarMonthStyles.calendarMonthDate}`; + const focusSelectorForSelectedDate = createFocusSelectorString(calendarMonthStyles.modifiers.selected); + const focusSelectorForUnselectedDate = createFocusSelectorString(calendarMonthStyles.modifiers.current); + return (
} showClose={false} diff --git a/packages/react-core/src/components/Popover/Popover.tsx b/packages/react-core/src/components/Popover/Popover.tsx index 2ce5c627a4f..5a9edfecccd 100644 --- a/packages/react-core/src/components/Popover/Popover.tsx +++ b/packages/react-core/src/components/Popover/Popover.tsx @@ -78,6 +78,10 @@ export interface PopoverProps { closeBtnAriaLabel?: string; /** Distance of the popover to its target. Defaults to 25. */ distance?: number; + /** The element to focus when the popover becomes visible. By default the first + * focusable element will receive focus. + */ + elementToFocus?: HTMLElement | SVGElement | string; /** * If true, tries to keep the popover in view by flipping it if necessary. * If the position is set to 'auto', this prop is ignored. @@ -269,6 +273,7 @@ export const Popover: React.FunctionComponent = ({ triggerRef, hasNoPadding = false, hasAutoWidth = false, + elementToFocus, ...rest }: PopoverProps) => { // could make this a prop in the future (true | false | 'toggle') @@ -285,7 +290,7 @@ export const Popover: React.FunctionComponent = ({ React.useEffect(() => { if (triggerManually) { if (isVisible) { - show(); + show(undefined, true); } else { hide(); } @@ -404,6 +409,7 @@ export const Popover: React.FunctionComponent = ({ hide(event); } }; + const content = ( = ({ focusTrapOptions={{ returnFocusOnDeactivate: true, clickOutsideDeactivates: true, + // FocusTrap's initialFocus can accept false as a value to prevent initial focus. + // We want to prevent this in case false is ever passed in. + initialFocus: elementToFocus || undefined, + checkCanFocusTrap: (containers) => + new Promise((resolve) => { + const interval = setInterval(() => { + if (containers.every((container) => getComputedStyle(container).visibility !== 'hidden')) { + resolve(); + clearInterval(interval); + } + }, 10); + }), tabbableOptions: { displayCheck: 'none' }, fallbackFocus: () => { diff --git a/packages/react-core/src/components/Popover/examples/Popover.md b/packages/react-core/src/components/Popover/examples/Popover.md index 3eb2be0243e..66cb0855941 100644 --- a/packages/react-core/src/components/Popover/examples/Popover.md +++ b/packages/react-core/src/components/Popover/examples/Popover.md @@ -89,3 +89,11 @@ Here the popover goes over the navigation, so the prop `appendTo` is set to the ```ts file="./PopoverAlert.tsx" ``` + +### Custom focus + +Use the `elementToFocus` property to customize which element inside the Popover receives focus when opened. + +```ts file="./PopoverCustomFocus.tsx" + +``` diff --git a/packages/react-core/src/components/Popover/examples/PopoverCustomFocus.tsx b/packages/react-core/src/components/Popover/examples/PopoverCustomFocus.tsx new file mode 100644 index 00000000000..5bf84ffdadf --- /dev/null +++ b/packages/react-core/src/components/Popover/examples/PopoverCustomFocus.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { Popover, Button } from '@patternfly/react-core'; + +export const PopoverCustomFocus: React.FunctionComponent = () => ( + Popover header
} + bodyContent={ +
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit.{' '} + +
+ } + footerContent={(hide) => ( + + )} + > + + +);