From bbdea8a976f792124e0952587bf039228dac505c Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Mon, 24 Apr 2023 14:53:23 -0400 Subject: [PATCH 01/17] feat(Select): add examples, add popper interface, fix focus --- .../src/components/Select/Select.tsx | 35 ++- .../src/components/Select/SelectGroup.tsx | 3 + .../src/components/Select/SelectOption.tsx | 24 +- .../src/components/Select/examples/Select.md | 40 +++- .../Select/examples/SelectBasic.tsx | 50 ++-- .../Select/examples/SelectGrouped.tsx | 24 +- .../examples/SelectMultiTypeaheadCheckbox.tsx | 206 ++++++++++++++++ .../SelectMultiTypeaheadCreatable.tsx | 225 ++++++++++++++++++ .../examples/SelectOptionVariations.tsx | 70 ++++++ .../examples/SelectTypeaheadCreatable.tsx | 216 +++++++++++++++++ .../Select/examples/SelectViewMore.tsx | 151 ++++++++++++ .../demos/ComposableMenu/ComposableMenu.md | 20 ++ .../examples/MenuWithFooter.tsx | 82 +++++++ .../react-core/src/helpers/Popper/Popper.tsx | 2 +- 14 files changed, 1116 insertions(+), 32 deletions(-) create mode 100644 packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx create mode 100644 packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCreatable.tsx create mode 100644 packages/react-core/src/components/Select/examples/SelectOptionVariations.tsx create mode 100644 packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx create mode 100644 packages/react-core/src/components/Select/examples/SelectViewMore.tsx create mode 100644 packages/react-core/src/demos/ComposableMenu/examples/MenuWithFooter.tsx diff --git a/packages/react-core/src/components/Select/Select.tsx b/packages/react-core/src/components/Select/Select.tsx index 3df1f197043..0516aea7da5 100644 --- a/packages/react-core/src/components/Select/Select.tsx +++ b/packages/react-core/src/components/Select/Select.tsx @@ -4,6 +4,25 @@ import { Menu, MenuContent, MenuProps } from '../Menu'; import { Popper, PopperProps } from '../../helpers/Popper/Popper'; import { getOUIAProps, OUIAProps, getDefaultOUIAId } from '../../helpers'; +export interface SelectPopperProps extends PopperProps { + /** popper direction */ + direction?: 'up' | 'down'; + /** popper position */ + position?: 'right' | 'left' | 'center'; + /** Custom width of the popper. If the value is "trigger", it will set the width to the select toggle's width */ + width?: string | 'trigger'; + /** Minimum width of the popper. If the value is "trigger", it will set the min width to the select toggle's width */ + minWidth?: string | 'trigger'; + /** Maximum width of the popper. If the value is "trigger", it will set the max width to the select toggle's width */ + maxWidth?: string | 'trigger'; + /** Enable to flip the popper when it reaches the boundary */ + enableFlip?: boolean; +} + +/** + * See the Menu documentation for additional props that may be passed. + */ + export interface SelectProps extends MenuProps, OUIAProps { /** Anything which can be rendered in a select */ children?: React.ReactNode; @@ -16,7 +35,11 @@ export interface SelectProps extends MenuProps, OUIAProps { /** Renderer for a custom select toggle. Forwards a ref to the toggle. */ toggle: (toggleRef: React.RefObject) => React.ReactNode; /** Function callback when user selects an option. */ - onSelect?: (event?: React.MouseEvent, itemId?: string | number) => void; + onSelect?: ( + event?: React.MouseEvent, + itemId?: string | number, + toggleRef?: React.RefObject + ) => void; /** Callback to allow the select component to change the open state of the menu. * Triggered by clicking outside of the menu, or by pressing either tab or escape. */ onOpenChange?: (isOpen: boolean) => void; @@ -29,7 +52,7 @@ export interface SelectProps extends MenuProps, OUIAProps { /** @beta Determines the accessible role of the select. For a checkbox select pass in "menu". */ role?: string; /** Additional properties to pass to the popper */ - popperProps?: Partial; + popperProps?: SelectPopperProps; } const SelectBase: React.FunctionComponent = ({ @@ -56,10 +79,12 @@ const SelectBase: React.FunctionComponent = ({ const handleMenuKeys = (event: KeyboardEvent) => { // Close the menu on tab or escape if onOpenChange is provided if ( - (isOpen && onOpenChange && menuRef.current?.contains(event.target as Node)) || - toggleRef.current?.contains(event.target as Node) + isOpen && + onOpenChange && + (menuRef.current?.contains(event.target as Node) || toggleRef.current?.contains(event.target as Node)) ) { if (event.key === 'Escape' || event.key === 'Tab') { + event.preventDefault(); onOpenChange(false); toggleRef.current?.focus(); } @@ -97,7 +122,7 @@ const SelectBase: React.FunctionComponent = ({ role={role} className={css(className)} ref={menuRef} - onSelect={(event, itemId) => onSelect && onSelect(event, itemId)} + onSelect={(event, itemId) => onSelect && onSelect(event, itemId, toggleRef)} isPlain={isPlain} selected={selected} {...getOUIAProps( diff --git a/packages/react-core/src/components/Select/SelectGroup.tsx b/packages/react-core/src/components/Select/SelectGroup.tsx index 645f5b2aa00..744de685390 100644 --- a/packages/react-core/src/components/Select/SelectGroup.tsx +++ b/packages/react-core/src/components/Select/SelectGroup.tsx @@ -2,6 +2,9 @@ import React from 'react'; import { css } from '@patternfly/react-styles'; import { MenuGroupProps, MenuGroup } from '../Menu'; +/** + * See the MenuGroup section of the Menu documentation for additional props that may be passed. + */ export interface SelectGroupProps extends Omit { /** Anything which can be rendered in a select group */ children: React.ReactNode; diff --git a/packages/react-core/src/components/Select/SelectOption.tsx b/packages/react-core/src/components/Select/SelectOption.tsx index 72f4fd2e0ae..5c941f39e7e 100644 --- a/packages/react-core/src/components/Select/SelectOption.tsx +++ b/packages/react-core/src/components/Select/SelectOption.tsx @@ -2,11 +2,17 @@ import React from 'react'; import { css } from '@patternfly/react-styles'; import { MenuItemProps, MenuItem } from '../Menu'; +/** + * See the MenuItem section of the Menu documentation for additional props that may be passed. + */ + export interface SelectOptionProps extends Omit { /** Anything which can be rendered in a select option */ children?: React.ReactNode; /** Classes applied to root element of select option */ className?: string; + /** @hide Forwarded ref */ + innerRef?: React.Ref; /** Identifies the component in the Select onSelect callback */ itemId?: any; /** Indicates the option has a checkbox */ @@ -17,15 +23,29 @@ export interface SelectOptionProps extends Omit { isSelected?: boolean; /** Indicates the option is focused */ isFocused?: boolean; + /** Render an external link icon on focus or hover, and set the link's + * "target" attribute to a value of "_blank". + */ + isExternalLink?: boolean; + /** Render option with icon */ + icon?: React.ReactNode; + /** Description of the option */ + description?: React.ReactNode; } -export const SelectOption: React.FunctionComponent = ({ +const SelectOptionBase: React.FunctionComponent = ({ children, className, + innerRef, ...props }: SelectOptionProps) => ( - + {children} ); + +export const SelectOption = React.forwardRef((props: SelectOptionProps, ref: React.Ref) => ( + +)); + SelectOption.displayName = 'SelectOption'; diff --git a/packages/react-core/src/components/Select/examples/Select.md b/packages/react-core/src/components/Select/examples/Select.md index 782430b7170..268859776f8 100644 --- a/packages/react-core/src/components/Select/examples/Select.md +++ b/packages/react-core/src/components/Select/examples/Select.md @@ -3,35 +3,71 @@ id: Select section: components subsection: menus cssPrefix: pf-c-select -propComponents: ['Select', 'SelectOption', 'SelectGroup', 'SelectList', 'MenuToggle'] +propComponents: ['Select', 'SelectOption', 'SelectGroup', 'SelectList', 'MenuToggle', 'SelectPopperProps'] ouia: true --- import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon'; +import BellIcon from '@patternfly/react-icons/dist/esm/icons/bell-icon'; ## Examples ### Single ```ts file="./SelectBasic.tsx" + +``` + +### Option variations + +```ts file="./SelectOptionVariations.tsx" + ``` ### Grouped single ```ts file="./SelectGrouped.tsx" + ``` ### Checkbox ```ts file="./SelectCheckbox.tsx" + ``` ### Typeahead ```ts file="./SelectTypeahead.tsx" + +``` + +### Typeahead with create option + +```ts file="./SelectTypeaheadCreatable.tsx" + ``` -### Multiple Typeahead +### Multiple typeahead with chips ```ts file="./SelectMultiTypeahead.tsx" + +``` + +### Multiple typeahead with create option + +```ts file="./SelectMultiTypeaheadCreatable.tsx" + +``` + +### Multiple typeahead with checkboxes + +```ts file="./SelectMultiTypeaheadCheckbox.tsx" + +``` + +### View more + +```ts file="./SelectViewMore.tsx" + ``` diff --git a/packages/react-core/src/components/Select/examples/SelectBasic.tsx b/packages/react-core/src/components/Select/examples/SelectBasic.tsx index f4084a53049..f750e9a0d08 100644 --- a/packages/react-core/src/components/Select/examples/SelectBasic.tsx +++ b/packages/react-core/src/components/Select/examples/SelectBasic.tsx @@ -1,21 +1,27 @@ import React from 'react'; -import { Select, SelectOption, SelectList, MenuToggle, MenuToggleElement } from '@patternfly/react-core'; +import { Select, SelectOption, SelectList, MenuToggle, MenuToggleElement, Checkbox } from '@patternfly/react-core'; export const SelectBasic: React.FunctionComponent = () => { const [isOpen, setIsOpen] = React.useState(false); const [selected, setSelected] = React.useState('Select a value'); + const [isDisabled, setIsDisabled] = React.useState(false); const menuRef = React.useRef(null); const onToggleClick = () => { setIsOpen(!isOpen); }; - const onSelect = (_event: React.MouseEvent | undefined, itemId: string | number | undefined) => { + const onSelect = ( + _event: React.MouseEvent | undefined, + itemId: string | number | undefined, + toggleRef: React.RefObject + ) => { // eslint-disable-next-line no-console console.log('selected', itemId); setSelected(itemId as string); setIsOpen(false); + toggleRef.current?.focus(); }; const toggle = (toggleRef: React.Ref) => ( @@ -23,6 +29,7 @@ export const SelectBasic: React.FunctionComponent = () => { ref={toggleRef} onClick={onToggleClick} isExpanded={isOpen} + isDisabled={isDisabled} style={ { width: '200px' @@ -34,20 +41,29 @@ export const SelectBasic: React.FunctionComponent = () => { ); return ( - + + setIsDisabled(checked)} + style={{ marginBottom: 20 }} + /> + + ); }; diff --git a/packages/react-core/src/components/Select/examples/SelectGrouped.tsx b/packages/react-core/src/components/Select/examples/SelectGrouped.tsx index 184188499ca..c88db3c334a 100644 --- a/packages/react-core/src/components/Select/examples/SelectGrouped.tsx +++ b/packages/react-core/src/components/Select/examples/SelectGrouped.tsx @@ -1,7 +1,15 @@ import React from 'react'; -import { Select, SelectOption, SelectList, SelectGroup, MenuToggle, MenuToggleElement } from '@patternfly/react-core'; +import { + Select, + SelectOption, + SelectList, + SelectGroup, + MenuToggle, + MenuToggleElement, + Divider +} from '@patternfly/react-core'; -export const SelectBasic: React.FunctionComponent = () => { +export const SelectGrouped: React.FunctionComponent = () => { const [isOpen, setIsOpen] = React.useState(false); const [selected, setSelected] = React.useState('Select a value'); const menuRef = React.useRef(null); @@ -10,12 +18,17 @@ export const SelectBasic: React.FunctionComponent = () => { setIsOpen(!isOpen); }; - const onSelect = (_event: React.MouseEvent | undefined, itemId: string | number | undefined) => { + const onSelect = ( + _event: React.MouseEvent | undefined, + itemId: string | number | undefined, + toggleRef: React.RefObject + ) => { // eslint-disable-next-line no-console console.log('selected', itemId); setSelected(itemId as string); setIsOpen(false); + toggleRef?.current.focus(); }; const toggle = (toggleRef: React.Ref) => ( @@ -35,12 +48,12 @@ export const SelectBasic: React.FunctionComponent = () => { return ( onSelect(selection as string)} + onOpenChange={() => setIsOpen(false)} + toggle={toggle} + > + + {selectOptions.map((option, index) => ( + + ))} + + + ); +}; diff --git a/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCreatable.tsx b/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCreatable.tsx new file mode 100644 index 00000000000..ea26ef6ab55 --- /dev/null +++ b/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCreatable.tsx @@ -0,0 +1,225 @@ +import React from 'react'; +import { + Select, + SelectOption, + SelectList, + SelectOptionProps, + MenuToggle, + MenuToggleElement, + TextInputGroup, + TextInputGroupMain, + TextInputGroupUtilities, + ChipGroup, + Chip, + Button +} from '@patternfly/react-core'; +import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon'; + +let initialSelectOptions: SelectOptionProps[] = [ + { itemId: 'Alabama', children: 'Alabama' }, + { itemId: 'Florida', children: 'Florida' }, + { itemId: 'New Jersey', children: 'New Jersey' }, + { itemId: 'New Mexico', children: 'New Mexico' }, + { itemId: 'New York', children: 'New York' }, + { itemId: 'North Carolina', children: 'North Carolina' } +]; + +export const SelectMultiTypeaheadCreatable: React.FunctionComponent = () => { + const [isOpen, setIsOpen] = React.useState(false); + const [inputValue, setInputValue] = React.useState(''); + const [selected, setSelected] = React.useState([]); + const [selectOptions, setSelectOptions] = React.useState(initialSelectOptions); + const [focusedItemIndex, setFocusedItemIndex] = React.useState(null); + const [activeItem, setActiveItem] = React.useState(null); + const [onCreation, setOnCreation] = React.useState(false); // Boolean to refresh filter state after new option is created + + const menuRef = React.useRef(null); + const textInputRef = React.useRef(); + + React.useEffect(() => { + let newSelectOptions: SelectOptionProps[] = initialSelectOptions; + + // Filter menu items based on the text input value when one exists + if (inputValue) { + newSelectOptions = initialSelectOptions.filter((menuItem) => + String(menuItem.children).toLowerCase().includes(inputValue.toLowerCase()) + ); + + // When no options are found after filtering, display creation option + if (!newSelectOptions.length) { + newSelectOptions = [{ isDisabled: false, children: `Create new option "${inputValue}"`, itemId: 'create' }]; + } + + // Open the menu when the input value changes and the new value is not empty + if (!isOpen) { + setIsOpen(true); + } + } + + setSelectOptions(newSelectOptions); + setFocusedItemIndex(null); + setActiveItem(null); + }, [inputValue, onCreation]); + + const handleMenuArrowKeys = (key: string) => { + let indexToFocus; + + if (isOpen) { + if (key === 'ArrowUp') { + // When no index is set or at the first index, focus to the last, otherwise decrement focus index + if (focusedItemIndex === null || focusedItemIndex === 0) { + indexToFocus = selectOptions.length - 1; + } else { + indexToFocus = focusedItemIndex - 1; + } + } + + if (key === 'ArrowDown') { + // When no index is set or at the last index, focus to the first, otherwise increment focus index + if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) { + indexToFocus = 0; + } else { + indexToFocus = focusedItemIndex + 1; + } + } + + setFocusedItemIndex(indexToFocus); + const focusedItem = selectOptions.filter((option) => !option.isDisabled)[indexToFocus]; + setActiveItem(`select-multi-create-typeahead-${focusedItem.itemId.replace(' ', '-')}`); + } + }; + + const onInputKeyDown = (event: React.KeyboardEvent) => { + const enabledMenuItems = selectOptions.filter((menuItem) => !menuItem.isDisabled); + const [firstMenuItem] = enabledMenuItems; + const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem; + + switch (event.key) { + // Select the first available option + case 'Enter': + if (!isOpen) { + setIsOpen((prevIsOpen) => !prevIsOpen); + } else if (isOpen && focusedItem.itemId !== 'no results') { + onSelect(focusedItem.itemId as string); + } + break; + case 'Tab': + case 'Escape': + setIsOpen(false); + setActiveItem(null); + break; + case 'ArrowUp': + case 'ArrowDown': + event.preventDefault(); + handleMenuArrowKeys(event.key); + break; + } + }; + + const onToggleClick = () => { + setIsOpen(!isOpen); + }; + + const onTextInputChange = (_event: React.FormEvent, value: string) => { + setInputValue(value); + }; + + const onSelect = (itemId: string) => { + if (itemId) { + if (itemId === 'create') { + if (!initialSelectOptions.some((item) => item.itemId === inputValue)) { + initialSelectOptions = [...initialSelectOptions, { itemId: inputValue, children: inputValue }]; + } + setSelected( + selected.includes(inputValue) + ? selected.filter((selection) => selection !== inputValue) + : [...selected, inputValue] + ); + setOnCreation(!onCreation); + } else { + // eslint-disable-next-line no-console + console.log('selected', itemId); + setSelected( + selected.includes(itemId) ? selected.filter((selection) => selection !== itemId) : [...selected, itemId] + ); + } + } + + textInputRef.current?.focus(); + }; + + const toggle = (toggleRef: React.Ref) => ( + + + + + {selected.map((selection, index) => ( + { + ev.stopPropagation(); + onSelect(selection); + }} + > + {selection} + + ))} + + + + {selected.length > 0 && ( + + )} + + + + ); + + return ( + + ); +}; diff --git a/packages/react-core/src/components/Select/examples/SelectOptionVariations.tsx b/packages/react-core/src/components/Select/examples/SelectOptionVariations.tsx new file mode 100644 index 00000000000..caf6940ed87 --- /dev/null +++ b/packages/react-core/src/components/Select/examples/SelectOptionVariations.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { Select, SelectOption, SelectList, MenuToggle, MenuToggleElement } from '@patternfly/react-core'; +import BellIcon from '@patternfly/react-icons/dist/esm/icons/bell-icon'; + +export const SelectOptionVariations: React.FunctionComponent = () => { + const [isOpen, setIsOpen] = React.useState(false); + const [selected, setSelected] = React.useState('Select a value'); + const menuRef = React.useRef(null); + + const onToggleClick = () => { + setIsOpen(!isOpen); + }; + + const onSelect = ( + _event: React.MouseEvent | undefined, + itemId: string | number | undefined, + toggleRef: React.RefObject + ) => { + // eslint-disable-next-line no-console + console.log('selected', itemId); + + setSelected(itemId as string); + setIsOpen(false); + toggleRef?.current.focus(); + }; + + const toggle = (toggleRef: React.Ref) => ( + + {selected} + + ); + + return ( + + ); +}; diff --git a/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx b/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx new file mode 100644 index 00000000000..a0407845722 --- /dev/null +++ b/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx @@ -0,0 +1,216 @@ +import React from 'react'; +import { + Select, + SelectOption, + SelectList, + SelectOptionProps, + MenuToggle, + MenuToggleElement, + TextInputGroup, + TextInputGroupMain, + TextInputGroupUtilities, + Button +} from '@patternfly/react-core'; +import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon'; + +let initialSelectOptions: SelectOptionProps[] = [ + { itemId: 'Alabama', children: 'Alabama' }, + { itemId: 'Florida', children: 'Florida' }, + { itemId: 'New Jersey', children: 'New Jersey' }, + { itemId: 'New Mexico', children: 'New Mexico' }, + { itemId: 'New York', children: 'New York' }, + { itemId: 'North Carolina', children: 'North Carolina' } +]; + +export const SelectTypeaheadCreatable: React.FunctionComponent = () => { + const [isOpen, setIsOpen] = React.useState(false); + const [selected, setSelected] = React.useState(''); + const [inputValue, setInputValue] = React.useState(''); + const [selectOptions, setSelectOptions] = React.useState(initialSelectOptions); + const [focusedItemIndex, setFocusedItemIndex] = React.useState(null); + const [activeItem, setActiveItem] = React.useState(null); + const [onCreation, setOnCreation] = React.useState(false); // Boolean to refresh filter state after new option is created + + const menuRef = React.useRef(null); + const textInputRef = React.useRef(); + + React.useEffect(() => { + let newSelectOptions: SelectOptionProps[] = initialSelectOptions; + + // Filter menu items based on the text input value when one exists + if (inputValue) { + newSelectOptions = initialSelectOptions.filter((menuItem) => + String(menuItem.children).toLowerCase().includes(inputValue.toLowerCase()) + ); + + // When no options are found after filtering, display creation option + if (!newSelectOptions.length) { + newSelectOptions = [{ isDisabled: false, children: `Create new option "${inputValue}"`, itemId: 'create' }]; + } + + // Open the menu when the input value changes and the new value is not empty + if (!isOpen) { + setIsOpen(true); + } + } + + setSelectOptions(newSelectOptions); + setActiveItem(null); + setFocusedItemIndex(null); + }, [inputValue, onCreation]); + + const onToggleClick = () => { + setIsOpen(!isOpen); + }; + + const onSelect = (_event: React.MouseEvent | undefined, itemId: string | number | undefined) => { + // eslint-disable-next-line no-console + + if (itemId) { + if (itemId === 'create') { + if (!initialSelectOptions.some((item) => item.itemId === inputValue)) { + initialSelectOptions = [...initialSelectOptions, { itemId: inputValue, children: inputValue }]; + } + setSelected(inputValue); + setOnCreation(!onCreation); + } else { + // eslint-disable-next-line no-console + console.log('selected', itemId); + setInputValue(itemId as string); + setSelected(itemId as string); + } + } + + setIsOpen(false); + setFocusedItemIndex(null); + setActiveItem(null); + }; + + const onTextInputChange = (_event: React.FormEvent, value: string) => { + setInputValue(value); + }; + + const handleMenuArrowKeys = (key: string) => { + let indexToFocus; + + if (isOpen) { + if (key === 'ArrowUp') { + // When no index is set or at the first index, focus to the last, otherwise decrement focus index + if (focusedItemIndex === null || focusedItemIndex === 0) { + indexToFocus = selectOptions.length - 1; + } else { + indexToFocus = focusedItemIndex - 1; + } + } + + if (key === 'ArrowDown') { + // When no index is set or at the last index, focus to the first, otherwise increment focus index + if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) { + indexToFocus = 0; + } else { + indexToFocus = focusedItemIndex + 1; + } + } + + setFocusedItemIndex(indexToFocus); + const focusedItem = selectOptions.filter((option) => !option.isDisabled)[indexToFocus]; + setActiveItem(`select-create-typeahead-${focusedItem.itemId.replace(' ', '-')}`); + } + }; + + const onInputKeyDown = (event: React.KeyboardEvent) => { + const enabledMenuItems = selectOptions.filter((option) => !option.isDisabled); + const [firstMenuItem] = enabledMenuItems; + const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem; + + switch (event.key) { + // Select the first available option + case 'Enter': + if (!isOpen) { + setIsOpen((prevIsOpen) => !prevIsOpen); + } else if (isOpen) { + onSelect(undefined, focusedItem.itemId as string); + setIsOpen((prevIsOpen) => !prevIsOpen); + setFocusedItemIndex(null); + setActiveItem(null); + } + + break; + case 'Tab': + case 'Escape': + setIsOpen(false); + setActiveItem(null); + break; + case 'ArrowUp': + case 'ArrowDown': + event.preventDefault(); + handleMenuArrowKeys(event.key); + break; + } + }; + + const toggle = (toggleRef: React.Ref) => ( + + + + + + {!!inputValue && ( + + )} + + + + ); + + return ( + + ); +}; diff --git a/packages/react-core/src/components/Select/examples/SelectViewMore.tsx b/packages/react-core/src/components/Select/examples/SelectViewMore.tsx new file mode 100644 index 00000000000..c0d56b5336d --- /dev/null +++ b/packages/react-core/src/components/Select/examples/SelectViewMore.tsx @@ -0,0 +1,151 @@ +import React from 'react'; +import { Select, SelectOption, SelectList, MenuToggle, MenuToggleElement, Spinner } from '@patternfly/react-core'; + +export const SelectViewMore: React.FunctionComponent = () => { + const [isOpen, setIsOpen] = React.useState(false); + const [selected, setSelected] = React.useState('Select a value'); + const [activeItem, setActiveItem] = React.useState(0); + const [isLoading, setIsLoading] = React.useState(false); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [selectOptions, setSelectOptions] = React.useState([ + + Option 1 + , + + Option 2 + , + + Option 3 + , + + Option 4 + , + + Option 5 + , + + Option 6 + , + + Option 7 + , + + Option 8 + , + + Option 9 + , + + Final Option 10 + + ]); + const [numOptions, setNumOptions] = React.useState(3); + const [visibleOptions, setVisibleOptions] = React.useState(selectOptions.slice(0, numOptions)); + const activeItemRef = React.useRef(null); + const viewMoreRef = React.useRef(null); + const menuRef = React.useRef(null); + + React.useEffect(() => { + activeItemRef.current?.focus(); + }, [visibleOptions]); + + const simulateNetworkCall = (networkCallback: () => void) => { + setTimeout(networkCallback, 2000); + }; + + const getNextValidItem = (startingIndex: number, maxLength: number) => { + let validItem; + for (let i = startingIndex; i < maxLength; i++) { + if (selectOptions[i].props.isDisabled) { + continue; + } else { + validItem = selectOptions[i]; + break; + } + } + return validItem; + }; + + const loadMoreOptions = () => { + const newLength = numOptions + 3 <= selectOptions.length ? numOptions + 3 : selectOptions.length; + const prevPosition = numOptions; + const nextValidItem = getNextValidItem(prevPosition, newLength); + + setNumOptions(newLength); + setIsLoading(false); + setActiveItem(nextValidItem.props.itemId); + setVisibleOptions(selectOptions.slice(0, newLength)); + }; + + const onViewMoreClick = () => { + setIsLoading(true); + simulateNetworkCall(() => { + loadMoreOptions(); + }); + }; + + const onToggleClick = () => { + setIsOpen(!isOpen); + }; + + const onSelect = ( + _event: React.MouseEvent | undefined, + itemId: string | number | undefined, + toggleRef: React.RefObject + ) => { + // eslint-disable-next-line no-console + console.log('selected', itemId); + + if (itemId !== 'loader') { + setSelected(itemId as string); + setIsOpen(false); + toggleRef?.current.focus(); + } + }; + + const toggle = (toggleRef: React.Ref) => ( + + {selected} + + ); + + return ( + + ); +}; diff --git a/packages/react-core/src/demos/ComposableMenu/ComposableMenu.md b/packages/react-core/src/demos/ComposableMenu/ComposableMenu.md index c06bda7770b..a5a806e89ac 100644 --- a/packages/react-core/src/demos/ComposableMenu/ComposableMenu.md +++ b/packages/react-core/src/demos/ComposableMenu/ComposableMenu.md @@ -32,36 +32,43 @@ Custom menus can be constructed using a composable approach by combining the [Me ### Composable simple dropdown ```ts file="./examples/ComposableSimpleDropdown.tsx" + ``` ### Composable actions menu ```ts file="./examples/ComposableActionsMenu.tsx" + ``` ### Composable simple select ```ts file="./examples/ComposableSimpleSelect.tsx" + ``` ### Composable simple checkbox select ```ts file="./examples/ComposableSimpleCheckboxSelect.tsx" + ``` ### Composable typeahead select ```ts file="./examples/ComposableTypeaheadSelect.tsx" + ``` ### Composable multiple typeahead select ```ts file="./examples/ComposableMultipleTypeaheadSelect.tsx" + ``` ### Composable drilldown menu ```ts isBeta file="./examples/ComposableDrilldownMenu.tsx" + ``` ### Composable tree view menu @@ -69,6 +76,7 @@ Custom menus can be constructed using a composable approach by combining the [Me When rendering a menu-like element that does not contain MenuItem components, [Panel](/components/panel) allows more flexible control and customization. ```ts file="./examples/ComposableTreeViewMenu.tsx" + ``` ### Composable flyout @@ -76,29 +84,41 @@ When rendering a menu-like element that does not contain MenuItem components, [P The flyout will automatically position to the left or top if it would otherwise go outside the window. The menu must be placed in a container outside the main content like Popper, [Popover](/components/popover) or [Tooltip](/components/tooltip) since it may go over the side nav. ```ts isBeta file="./examples/ComposableFlyout.tsx" + ``` ### Composable application launcher ```ts file="./examples/ComposableApplicationLauncher.tsx" + ``` ### Composable context selector ```ts file="./examples/ComposableContextSelector.tsx" + ``` ### Composable options menu variants ```ts file="./examples/ComposableOptionsMenuVariants.tsx" + ``` ### Composable dropdown variants ```ts file="./examples/ComposableDropdwnVariants.tsx" + ``` ### Composable date select ```ts file="./examples/ComposableDateSelect.tsx" + +``` + +### Select with footer + +```ts file="./examples/MenuWithFooter.tsx" + ``` diff --git a/packages/react-core/src/demos/ComposableMenu/examples/MenuWithFooter.tsx b/packages/react-core/src/demos/ComposableMenu/examples/MenuWithFooter.tsx new file mode 100644 index 00000000000..e878dca302f --- /dev/null +++ b/packages/react-core/src/demos/ComposableMenu/examples/MenuWithFooter.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { MenuToggle, Menu, MenuContent, MenuList, MenuItem, Popper, MenuFooter } from '@patternfly/react-core'; + +export const MenuWithFooter: React.FunctionComponent = () => { + const [isOpen, setIsOpen] = React.useState(false); + const [selected, setSelected] = React.useState('Select a value'); + const toggleRef = React.useRef(null); + const menuRef = React.useRef(null); + + const handleMenuKeys = (event: KeyboardEvent) => { + if (isOpen && menuRef.current?.contains(event.target as Node)) { + if (event.key === 'Escape') { + // Tab should not close the menu to enable keyboard access to the footer element + setIsOpen(!isOpen); + toggleRef.current?.focus(); + } + } + }; + + const handleClickOutside = (event: MouseEvent) => { + if (isOpen && !menuRef.current?.contains(event.target as Node)) { + setIsOpen(false); + } + }; + + React.useEffect(() => { + window.addEventListener('keydown', handleMenuKeys); + window.addEventListener('click', handleClickOutside); + return () => { + window.removeEventListener('keydown', handleMenuKeys); + window.removeEventListener('click', handleClickOutside); + }; + }, [isOpen, menuRef]); + + const onToggleClick = (ev: React.MouseEvent) => { + ev.stopPropagation(); // Stop handleClickOutside from handling + setTimeout(() => { + if (menuRef.current) { + const firstElement = menuRef.current.querySelector('li > button:not(:disabled), li > a:not(:disabled)'); + firstElement && (firstElement as HTMLElement).focus(); + } + }, 0); + setIsOpen(!isOpen); + }; + + const toggle = ( + + {selected} + + ); + + function onSelect(event: React.MouseEvent | undefined, itemId: string | number | undefined) { + if (typeof itemId === 'undefined') { + return; + } + + setSelected(itemId.toString()); + } + + const menu = ( + + + + Option 1 + Option 2 + Option 3 + + + + + ); + return ; +}; diff --git a/packages/react-core/src/helpers/Popper/Popper.tsx b/packages/react-core/src/helpers/Popper/Popper.tsx index b6ad58ada3e..75cfcec7687 100644 --- a/packages/react-core/src/helpers/Popper/Popper.tsx +++ b/packages/react-core/src/helpers/Popper/Popper.tsx @@ -68,7 +68,7 @@ export interface PopperProps { position?: 'right' | 'left' | 'center'; /** Instead of direction and position can set the placement of the popper */ placement?: Placement; - /** Custsom width of the popper. If the value is "trigger", it will set the width to the trigger element's width */ + /** Custom width of the popper. If the value is "trigger", it will set the width to the trigger element's width */ width?: string | 'trigger'; /** Minimum width of the popper. If the value is "trigger", it will set the min width to the trigger element's width */ minWidth?: string | 'trigger'; From 5f5a25ff8cb54e1389797b4af85702d2369bb7b2 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Wed, 3 May 2023 13:55:41 -0400 Subject: [PATCH 02/17] feedback round 1 --- .../src/components/Select/Select.tsx | 13 +-- .../src/components/Select/examples/Select.md | 10 +++ .../Select/examples/SelectFooter.tsx | 64 +++++++++++++++ .../examples/SelectMultiTypeaheadCheckbox.tsx | 3 +- .../examples/SelectOptionVariations.tsx | 8 +- .../Select/examples/SelectTypeahead.tsx | 23 +++--- .../examples/SelectTypeaheadCreatable.tsx | 27 +++--- .../demos/ComposableMenu/ComposableMenu.md | 6 -- .../examples/MenuWithFooter.tsx | 82 ------------------- 9 files changed, 121 insertions(+), 115 deletions(-) create mode 100644 packages/react-core/src/components/Select/examples/SelectFooter.tsx delete mode 100644 packages/react-core/src/demos/ComposableMenu/examples/MenuWithFooter.tsx diff --git a/packages/react-core/src/components/Select/Select.tsx b/packages/react-core/src/components/Select/Select.tsx index 0516aea7da5..1f3af5dc919 100644 --- a/packages/react-core/src/components/Select/Select.tsx +++ b/packages/react-core/src/components/Select/Select.tsx @@ -5,9 +5,9 @@ import { Popper, PopperProps } from '../../helpers/Popper/Popper'; import { getOUIAProps, OUIAProps, getDefaultOUIAId } from '../../helpers'; export interface SelectPopperProps extends PopperProps { - /** popper direction */ + /** Vertical direction of the popper. If enableFlip is set to true, this will set the initial direction before the popper flips. */ direction?: 'up' | 'down'; - /** popper position */ + /** Horizontal position of the popper */ position?: 'right' | 'left' | 'center'; /** Custom width of the popper. If the value is "trigger", it will set the width to the select toggle's width */ width?: string | 'trigger'; @@ -41,8 +41,10 @@ export interface SelectProps extends MenuProps, OUIAProps { toggleRef?: React.RefObject ) => void; /** Callback to allow the select component to change the open state of the menu. - * Triggered by clicking outside of the menu, or by pressing either tab or escape. */ + * Triggered by clicking outside of the menu, or by pressing either tab or escape (or specificed in onOpenChangeKeys). */ onOpenChange?: (isOpen: boolean) => void; + /** Keys that trigger onOpenChange, defaults to tab and escape. */ + onOpenChangeKeys?: string[]; /** Indicates if the select should be without the outer box-shadow */ isPlain?: boolean; /** @hide Forwarded ref */ @@ -63,6 +65,7 @@ const SelectBase: React.FunctionComponent = ({ selected, toggle, onOpenChange, + onOpenChangeKeys = ['Escape', 'Tab'], isPlain, innerRef, zIndex = 9999, @@ -83,7 +86,7 @@ const SelectBase: React.FunctionComponent = ({ onOpenChange && (menuRef.current?.contains(event.target as Node) || toggleRef.current?.contains(event.target as Node)) ) { - if (event.key === 'Escape' || event.key === 'Tab') { + if (onOpenChangeKeys.includes(event.key)) { event.preventDefault(); onOpenChange(false); toggleRef.current?.focus(); @@ -115,7 +118,7 @@ const SelectBase: React.FunctionComponent = ({ window.removeEventListener('keydown', handleMenuKeys); window.removeEventListener('click', handleClick); }; - }, [isOpen, menuRef, onOpenChange]); + }, [isOpen, menuRef, onOpenChange, onOpenChangeKeys]); const menu = ( { + const [isOpen, setIsOpen] = React.useState(false); + const [selected, setSelected] = React.useState('Select a value'); + const menuRef = React.useRef(null); + + const onToggleClick = (ev: React.MouseEvent) => { + ev.stopPropagation(); // Stop handleClickOutside from handling + setTimeout(() => { + if (menuRef.current) { + const firstElement = menuRef.current.querySelector('li > button:not(:disabled), li > a:not(:disabled)'); + firstElement && (firstElement as HTMLElement).focus(); + } + }, 0); + setIsOpen(!isOpen); + }; + + const toggle = (toggleRef) => ( + + {selected} + + ); + + function onSelect(event: React.MouseEvent | undefined, itemId: string | number | undefined) { + if (typeof itemId === 'undefined') { + return; + } + + setSelected(itemId.toString()); + } + + return ( + + ); +}; diff --git a/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx b/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx index 5a4c5abc8bd..1b117cfa3f1 100644 --- a/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx +++ b/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx @@ -179,6 +179,7 @@ export const SelectMultiTypeaheadCheckbox: React.FunctionComponent = () => { return ( ); From 4a92893e36e1af67947b8c953077480eaded80f4 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Tue, 16 May 2023 10:28:01 -0400 Subject: [PATCH 09/17] remove unneeded ref, add focus flag & comments --- .../src/components/Select/Select.tsx | 10 ++++++++-- .../Select/examples/SelectBasic.tsx | 10 ++-------- .../Select/examples/SelectCheckbox.tsx | 4 +--- .../Select/examples/SelectFooter.tsx | 2 -- .../Select/examples/SelectGrouped.tsx | 10 ++-------- .../Select/examples/SelectMultiTypeahead.tsx | 19 +++++++------------ .../examples/SelectMultiTypeaheadCheckbox.tsx | 3 --- .../SelectMultiTypeaheadCreatable.tsx | 3 --- .../examples/SelectOptionVariations.tsx | 10 ++-------- .../Select/examples/SelectTypeahead.tsx | 3 --- .../examples/SelectTypeaheadCreatable.tsx | 3 --- .../Select/examples/SelectViewMore.tsx | 6 ++---- 12 files changed, 24 insertions(+), 59 deletions(-) diff --git a/packages/react-core/src/components/Select/Select.tsx b/packages/react-core/src/components/Select/Select.tsx index 5979f55255b..f5da996c545 100644 --- a/packages/react-core/src/components/Select/Select.tsx +++ b/packages/react-core/src/components/Select/Select.tsx @@ -34,7 +34,9 @@ export interface SelectProps extends MenuProps, OUIAProps { selected?: any | any[]; /** Renderer for a custom select toggle. Forwards a ref to the toggle. */ toggle: (toggleRef: React.RefObject) => React.ReactNode; - /** Function callback when user selects an option. */ + /** Flag indicating the toggle should be focused after a selection. */ + shouldFocusToggleOnSelect?: boolean; + /** Function callback when user selects an option. The toggleRef property is optional to allow customization of focus management. */ onSelect?: ( event?: React.MouseEvent, itemId?: string | number, @@ -64,6 +66,7 @@ const SelectBase: React.FunctionComponent = ({ isOpen, selected, toggle, + shouldFocusToggleOnSelect = false, onOpenChange, onOpenChangeKeys = ['Escape', 'Tab'], isPlain, @@ -124,7 +127,10 @@ const SelectBase: React.FunctionComponent = ({ role={role} className={css(className)} ref={menuRef} - onSelect={(event, itemId) => onSelect && onSelect(event, itemId, toggleRef)} + onSelect={(event, itemId) => { + onSelect && onSelect(event, itemId, toggleRef); + shouldFocusToggleOnSelect && toggleRef.current.focus(); + }} isPlain={isPlain} selected={selected} {...getOUIAProps( diff --git a/packages/react-core/src/components/Select/examples/SelectBasic.tsx b/packages/react-core/src/components/Select/examples/SelectBasic.tsx index f750e9a0d08..9a7f56af78c 100644 --- a/packages/react-core/src/components/Select/examples/SelectBasic.tsx +++ b/packages/react-core/src/components/Select/examples/SelectBasic.tsx @@ -5,23 +5,17 @@ export const SelectBasic: React.FunctionComponent = () => { const [isOpen, setIsOpen] = React.useState(false); const [selected, setSelected] = React.useState('Select a value'); const [isDisabled, setIsDisabled] = React.useState(false); - const menuRef = React.useRef(null); const onToggleClick = () => { setIsOpen(!isOpen); }; - const onSelect = ( - _event: React.MouseEvent | undefined, - itemId: string | number | undefined, - toggleRef: React.RefObject - ) => { + const onSelect = (_event: React.MouseEvent | undefined, itemId: string | number | undefined) => { // eslint-disable-next-line no-console console.log('selected', itemId); setSelected(itemId as string); setIsOpen(false); - toggleRef.current?.focus(); }; const toggle = (toggleRef: React.Ref) => ( @@ -51,12 +45,12 @@ export const SelectBasic: React.FunctionComponent = () => { /> { const [isOpen, setIsOpen] = React.useState(false); const [selected, setSelected] = React.useState('Select a value'); - const menuRef = React.useRef(null); const onToggleClick = () => { setIsOpen(!isOpen); @@ -39,7 +38,6 @@ export const SelectFooter: React.FunctionComponent = () => { onOpenChange={(isOpen) => setIsOpen(isOpen)} onOpenChangeKeys={['Escape']} toggle={toggle} - ref={menuRef} id="menu-with-footer" onSelect={onSelect} selected={selected} diff --git a/packages/react-core/src/components/Select/examples/SelectGrouped.tsx b/packages/react-core/src/components/Select/examples/SelectGrouped.tsx index c88db3c334a..03621ee9ffb 100644 --- a/packages/react-core/src/components/Select/examples/SelectGrouped.tsx +++ b/packages/react-core/src/components/Select/examples/SelectGrouped.tsx @@ -12,23 +12,17 @@ import { export const SelectGrouped: React.FunctionComponent = () => { const [isOpen, setIsOpen] = React.useState(false); const [selected, setSelected] = React.useState('Select a value'); - const menuRef = React.useRef(null); const onToggleClick = () => { setIsOpen(!isOpen); }; - const onSelect = ( - _event: React.MouseEvent | undefined, - itemId: string | number | undefined, - toggleRef: React.RefObject - ) => { + const onSelect = (_event: React.MouseEvent | undefined, itemId: string | number | undefined) => { // eslint-disable-next-line no-console console.log('selected', itemId); setSelected(itemId as string); setIsOpen(false); - toggleRef?.current.focus(); }; const toggle = (toggleRef: React.Ref) => ( @@ -49,12 +43,12 @@ export const SelectGrouped: React.FunctionComponent = () => { return ( onSelect(selection as string)} diff --git a/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx b/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx index 1b117cfa3f1..b57aa0e8193 100644 --- a/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx +++ b/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx @@ -30,8 +30,6 @@ export const SelectMultiTypeaheadCheckbox: React.FunctionComponent = () => { const [focusedItemIndex, setFocusedItemIndex] = React.useState(null); const [activeItem, setActiveItem] = React.useState(null); const [placeholder, setPlaceholder] = React.useState('0 items selected'); - - const menuRef = React.useRef(null); const textInputRef = React.useRef(); React.useEffect(() => { @@ -181,7 +179,6 @@ export const SelectMultiTypeaheadCheckbox: React.FunctionComponent = () => { onSelect(selection as string)} diff --git a/packages/react-core/src/components/Select/examples/SelectOptionVariations.tsx b/packages/react-core/src/components/Select/examples/SelectOptionVariations.tsx index 1a1996b9e3e..8bc3d1d35de 100644 --- a/packages/react-core/src/components/Select/examples/SelectOptionVariations.tsx +++ b/packages/react-core/src/components/Select/examples/SelectOptionVariations.tsx @@ -5,23 +5,17 @@ import BellIcon from '@patternfly/react-icons/dist/esm/icons/bell-icon'; export const SelectOptionVariations: React.FunctionComponent = () => { const [isOpen, setIsOpen] = React.useState(false); const [selected, setSelected] = React.useState('Select a value'); - const menuRef = React.useRef(null); const onToggleClick = () => { setIsOpen(!isOpen); }; - const onSelect = ( - _event: React.MouseEvent | undefined, - itemId: string | number | undefined, - toggleRef: React.RefObject - ) => { + const onSelect = (_event: React.MouseEvent | undefined, itemId: string | number | undefined) => { // eslint-disable-next-line no-console console.log('selected', itemId); setSelected(itemId as string); setIsOpen(false); - toggleRef?.current.focus(); }; const toggle = (toggleRef: React.Ref) => ( @@ -42,12 +36,12 @@ export const SelectOptionVariations: React.FunctionComponent = () => { return ( { const [focusedItemIndex, setFocusedItemIndex] = React.useState(null); const [activeItem, setActiveItem] = React.useState(null); const [onCreation, setOnCreation] = React.useState(false); // Boolean to refresh filter state after new option is created - - const menuRef = React.useRef(null); const textInputRef = React.useRef(); React.useEffect(() => { @@ -196,7 +194,6 @@ export const SelectTypeaheadCreatable: React.FunctionComponent = () => { return ( Date: Wed, 17 May 2023 13:20:03 -0400 Subject: [PATCH 10/17] toggleRef option --- .../src/components/Dropdown/Dropdown.tsx | 29 ++++++++++++------- .../src/components/Select/Select.tsx | 27 ++++++++--------- .../Select/examples/SelectViewMore.tsx | 14 ++++----- 3 files changed, 38 insertions(+), 32 deletions(-) diff --git a/packages/react-core/src/components/Dropdown/Dropdown.tsx b/packages/react-core/src/components/Dropdown/Dropdown.tsx index fa2688d35f8..26837dc1ec5 100644 --- a/packages/react-core/src/components/Dropdown/Dropdown.tsx +++ b/packages/react-core/src/components/Dropdown/Dropdown.tsx @@ -27,16 +27,16 @@ export interface DropdownProps extends MenuProps, OUIAProps { children?: React.ReactNode; /** Classes applied to root element of dropdown. */ className?: string; - /** Renderer for a custom dropdown toggle. Forwards a ref to the toggle. */ - toggle: (toggleRef: React.RefObject) => React.ReactNode; + /** Select toggle. May be a direct ReactNode combined with the toggleRef property, or a renderer for a custom dropdown toggle which forwards a ref to the toggle. */ + toggle: React.ReactNode | ((toggleRef: React.RefObject) => React.ReactNode); + /** Ref of the dropdown toggle ReactNode. */ + toggleRef?: React.RefObject; /** Flag to indicate if menu is opened.*/ isOpen?: boolean; + /** Flag indicating the toggle should be focused after a selection. If this use case is too restrictive, the optional toggleRef property with a node toggle may be used to control focus. */ + shouldFocusToggleOnSelect?: boolean; /** Function callback called when user selects item. */ - onSelect?: ( - event?: React.MouseEvent, - itemId?: string | number, - toggleRef?: React.RefObject - ) => void; + onSelect?: (event?: React.MouseEvent, itemId?: string | number) => void; /** Callback to allow the dropdown component to change the open state of the menu. * Triggered by clicking outside of the menu, or by pressing either tab or escape. */ onOpenChange?: (isOpen: boolean) => void; @@ -62,6 +62,8 @@ const DropdownBase: React.FunctionComponent = ({ onSelect, isOpen, toggle, + toggleRef: toggleRefProp, + shouldFocusToggleOnSelect = false, onOpenChange, isPlain, isScrollable, @@ -73,10 +75,12 @@ const DropdownBase: React.FunctionComponent = ({ ...props }: DropdownProps) => { const localMenuRef = React.useRef(); - const toggleRef = React.useRef(); + const localToggleRef = React.useRef(); const ouiaProps = useOUIAProps(Dropdown.displayName, ouiaId, ouiaSafe); const menuRef = (innerRef as React.RefObject) || localMenuRef; + const toggleRef = (toggleRefProp as React.RefObject) || localToggleRef; + React.useEffect(() => { const handleMenuKeys = (event: KeyboardEvent) => { // Close the menu on tab or escape if onOpenChange is provided @@ -119,13 +123,16 @@ const DropdownBase: React.FunctionComponent = ({ window.removeEventListener('keydown', handleMenuKeys); window.removeEventListener('click', handleClick); }; - }, [isOpen, menuRef, onOpenChange]); + }, [isOpen, menuRef, toggleRef, onOpenChange]); const menu = ( onSelect && onSelect(event, itemId, toggleRef)} + onSelect={(event, itemId) => { + onSelect && onSelect(event, itemId); + shouldFocusToggleOnSelect && toggleRef.current.focus(); + }} isPlain={isPlain} isScrollable={isScrollable} {...props} @@ -136,7 +143,7 @@ const DropdownBase: React.FunctionComponent = ({ ); return ( ) => React.ReactNode; - /** Flag indicating the toggle should be focused after a selection. */ + /** Select toggle. May be a direct ReactNode combined with the toggleRef property, or a renderer for a custom select toggle which forwards a ref to the toggle. */ + toggle: React.ReactNode | ((toggleRef: React.RefObject) => React.ReactNode); + /** Ref of the select toggle ReactNode. */ + toggleRef?: React.RefObject; + /** Flag indicating the toggle should be focused after a selection. If this use case is too restrictive, the optional toggleRef property with a node toggle may be used to control focus. */ shouldFocusToggleOnSelect?: boolean; - /** Function callback when user selects an option. The toggleRef property is optional to allow customization of focus management. */ - onSelect?: ( - event?: React.MouseEvent, - itemId?: string | number, - toggleRef?: React.RefObject - ) => void; + /** Function callback when user selects an option. */ + onSelect?: (event?: React.MouseEvent, itemId?: string | number) => void; /** Callback to allow the select component to change the open state of the menu. * Triggered by clicking outside of the menu, or by pressing either tab or escape (or specificed in onOpenChangeKeys). */ onOpenChange?: (isOpen: boolean) => void; @@ -66,6 +64,7 @@ const SelectBase: React.FunctionComponent = ({ isOpen, selected, toggle, + toggleRef: toggleRefProp, shouldFocusToggleOnSelect = false, onOpenChange, onOpenChangeKeys = ['Escape', 'Tab'], @@ -77,9 +76,11 @@ const SelectBase: React.FunctionComponent = ({ ...props }: SelectProps & OUIAProps) => { const localMenuRef = React.useRef(); - const toggleRef = React.useRef(); + const localToggleRef = React.useRef(); const menuRef = (innerRef as React.RefObject) || localMenuRef; + const toggleRef = (toggleRefProp as React.RefObject) || localToggleRef; + React.useEffect(() => { const handleMenuKeys = (event: KeyboardEvent) => { // Close the menu on tab or escape if onOpenChange is provided @@ -120,7 +121,7 @@ const SelectBase: React.FunctionComponent = ({ window.removeEventListener('keydown', handleMenuKeys); window.removeEventListener('click', handleClick); }; - }, [isOpen, menuRef, onOpenChange, onOpenChangeKeys]); + }, [isOpen, menuRef, toggleRef, onOpenChange, onOpenChangeKeys]); const menu = ( = ({ className={css(className)} ref={menuRef} onSelect={(event, itemId) => { - onSelect && onSelect(event, itemId, toggleRef); + onSelect && onSelect(event, itemId); shouldFocusToggleOnSelect && toggleRef.current.focus(); }} isPlain={isPlain} @@ -145,7 +146,7 @@ const SelectBase: React.FunctionComponent = ({ ); return ( { const [isOpen, setIsOpen] = React.useState(false); @@ -43,6 +43,7 @@ export const SelectViewMore: React.FunctionComponent = () => { const [visibleOptions, setVisibleOptions] = React.useState(selectOptions.slice(0, numOptions)); const activeItemRef = React.useRef(null); const viewMoreRef = React.useRef(null); + const toggleRef = React.useRef(null); React.useEffect(() => { activeItemRef.current?.focus(); @@ -87,22 +88,18 @@ export const SelectViewMore: React.FunctionComponent = () => { setIsOpen(!isOpen); }; - const onSelect = ( - _event: React.MouseEvent | undefined, - itemId: string | number | undefined, - toggleRef: React.RefObject // To further customize toggle behavior, use this optional property - ) => { + const onSelect = (_event: React.MouseEvent | undefined, itemId: string | number | undefined) => { // eslint-disable-next-line no-console console.log('selected', itemId); if (itemId !== 'loader') { setSelected(itemId as string); setIsOpen(false); - toggleRef?.current.focus(); // Only focus the toggle when a non-loader option is selected + toggleRef?.current?.focus(); // Only focus the toggle when a non-loader option is selected } }; - const toggle = (toggleRef: React.Ref) => ( + const toggle = ( { onSelect={onSelect} onOpenChange={(isOpen) => setIsOpen(isOpen)} toggle={toggle} + toggleRef={toggleRef} > {visibleOptions.map((option) => { From a355c7559f75c2efb607009831ee7966f48bee65 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Wed, 17 May 2023 13:33:55 -0400 Subject: [PATCH 11/17] update dropdown examples --- .../src/components/Dropdown/examples/DropdownBasic.tsx | 8 ++------ .../Dropdown/examples/DropdownWithDescriptions.tsx | 8 ++------ .../components/Dropdown/examples/DropdownWithGroups.tsx | 8 ++------ .../Dropdown/examples/DropdownWithKebabToggle.tsx | 8 ++------ 4 files changed, 8 insertions(+), 24 deletions(-) diff --git a/packages/react-core/src/components/Dropdown/examples/DropdownBasic.tsx b/packages/react-core/src/components/Dropdown/examples/DropdownBasic.tsx index f472964130c..3b3562b9f9a 100644 --- a/packages/react-core/src/components/Dropdown/examples/DropdownBasic.tsx +++ b/packages/react-core/src/components/Dropdown/examples/DropdownBasic.tsx @@ -8,15 +8,10 @@ export const DropdownBasic: React.FunctionComponent = () => { setIsOpen(!isOpen); }; - const onSelect = ( - _event: React.MouseEvent | undefined, - itemId: string | number | undefined, - toggleRef: React.RefObject - ) => { + const onSelect = (_event: React.MouseEvent | undefined, itemId: string | number | undefined) => { // eslint-disable-next-line no-console console.log('selected', itemId); setIsOpen(false); - toggleRef?.current.focus(); }; return ( @@ -30,6 +25,7 @@ export const DropdownBasic: React.FunctionComponent = () => { )} ouiaId="BasicDropdown" + shouldFocusToggleOnSelect > diff --git a/packages/react-core/src/components/Dropdown/examples/DropdownWithDescriptions.tsx b/packages/react-core/src/components/Dropdown/examples/DropdownWithDescriptions.tsx index ce30700be51..2b7b4d7d6a6 100644 --- a/packages/react-core/src/components/Dropdown/examples/DropdownWithDescriptions.tsx +++ b/packages/react-core/src/components/Dropdown/examples/DropdownWithDescriptions.tsx @@ -8,15 +8,10 @@ export const DropdownWithDescriptions: React.FunctionComponent = () => { setIsOpen(!isOpen); }; - const onSelect = ( - _event: React.MouseEvent | undefined, - itemId: string | number | undefined, - toggleRef: React.RefObject - ) => { + const onSelect = (_event: React.MouseEvent | undefined, itemId: string | number | undefined) => { // eslint-disable-next-line no-console console.log('selected', itemId); setIsOpen(false); - toggleRef?.current.focus(); }; return ( @@ -29,6 +24,7 @@ export const DropdownWithDescriptions: React.FunctionComponent = () => { Dropdown )} + shouldFocusToggleOnSelect > diff --git a/packages/react-core/src/components/Dropdown/examples/DropdownWithGroups.tsx b/packages/react-core/src/components/Dropdown/examples/DropdownWithGroups.tsx index a846e06e089..619528ed40f 100644 --- a/packages/react-core/src/components/Dropdown/examples/DropdownWithGroups.tsx +++ b/packages/react-core/src/components/Dropdown/examples/DropdownWithGroups.tsx @@ -16,15 +16,10 @@ export const DropdownWithGroups: React.FunctionComponent = () => { setIsOpen(!isOpen); }; - const onSelect = ( - _event: React.MouseEvent | undefined, - itemId: string | number | undefined, - toggleRef: React.RefObject - ) => { + const onSelect = (_event: React.MouseEvent | undefined, itemId: string | number | undefined) => { // eslint-disable-next-line no-console console.log('selected', itemId); setIsOpen(false); - toggleRef?.current.focus(); }; return ( @@ -37,6 +32,7 @@ export const DropdownWithGroups: React.FunctionComponent = () => { Dropdown )} + shouldFocusToggleOnSelect > diff --git a/packages/react-core/src/components/Dropdown/examples/DropdownWithKebabToggle.tsx b/packages/react-core/src/components/Dropdown/examples/DropdownWithKebabToggle.tsx index a3fb6202e75..0b41bc43b10 100644 --- a/packages/react-core/src/components/Dropdown/examples/DropdownWithKebabToggle.tsx +++ b/packages/react-core/src/components/Dropdown/examples/DropdownWithKebabToggle.tsx @@ -9,15 +9,10 @@ export const DropdownWithKebab: React.FunctionComponent = () => { setIsOpen(!isOpen); }; - const onSelect = ( - _event: React.MouseEvent | undefined, - itemId: string | number | undefined, - toggleRef: React.RefObject - ) => { + const onSelect = (_event: React.MouseEvent | undefined, itemId: string | number | undefined) => { // eslint-disable-next-line no-console console.log('selected', itemId); setIsOpen(false); - toggleRef?.current.focus(); }; return ( @@ -36,6 +31,7 @@ export const DropdownWithKebab: React.FunctionComponent = () => { )} + shouldFocusToggleOnSelect > From 0f4aebae16c405abeca6597e56f31063cd5ca243 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Wed, 17 May 2023 14:05:21 -0400 Subject: [PATCH 12/17] snap --- .../__snapshots__/DatePicker.test.tsx.snap | 55 ++++++++++--------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/packages/react-core/src/components/DatePicker/__tests__/__snapshots__/DatePicker.test.tsx.snap b/packages/react-core/src/components/DatePicker/__tests__/__snapshots__/DatePicker.test.tsx.snap index c10f6ef54e2..735e0f44695 100644 --- a/packages/react-core/src/components/DatePicker/__tests__/__snapshots__/DatePicker.test.tsx.snap +++ b/packages/react-core/src/components/DatePicker/__tests__/__snapshots__/DatePicker.test.tsx.snap @@ -107,40 +107,45 @@ exports[`With popover opened 1`] = ` >
- Month - - + +
Date: Wed, 17 May 2023 16:05:59 -0400 Subject: [PATCH 13/17] update desc --- packages/react-core/src/components/Dropdown/Dropdown.tsx | 4 ++-- packages/react-core/src/components/Select/Select.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-core/src/components/Dropdown/Dropdown.tsx b/packages/react-core/src/components/Dropdown/Dropdown.tsx index 26837dc1ec5..2b0a04d23d0 100644 --- a/packages/react-core/src/components/Dropdown/Dropdown.tsx +++ b/packages/react-core/src/components/Dropdown/Dropdown.tsx @@ -27,9 +27,9 @@ export interface DropdownProps extends MenuProps, OUIAProps { children?: React.ReactNode; /** Classes applied to root element of dropdown. */ className?: string; - /** Select toggle. May be a direct ReactNode combined with the toggleRef property, or a renderer for a custom dropdown toggle which forwards a ref to the toggle. */ + /** Dropdown toggle. The toggle should either be a renderer function which forwards the given toggle ref, or a direct ReactNode that should be passed along with the toggleRef property. */ toggle: React.ReactNode | ((toggleRef: React.RefObject) => React.ReactNode); - /** Ref of the dropdown toggle ReactNode. */ + /** Ref of the dropdown toggle. This property should be passed when toggle is assigned a node, to enable built in basic interaction handling. */ toggleRef?: React.RefObject; /** Flag to indicate if menu is opened.*/ isOpen?: boolean; diff --git a/packages/react-core/src/components/Select/Select.tsx b/packages/react-core/src/components/Select/Select.tsx index 9208e2ccd43..acf511239d5 100644 --- a/packages/react-core/src/components/Select/Select.tsx +++ b/packages/react-core/src/components/Select/Select.tsx @@ -32,9 +32,9 @@ export interface SelectProps extends MenuProps, OUIAProps { isOpen?: boolean; /** Single itemId for single select menus, or array of itemIds for multi select. You can also specify isSelected on the SelectOption. */ selected?: any | any[]; - /** Select toggle. May be a direct ReactNode combined with the toggleRef property, or a renderer for a custom select toggle which forwards a ref to the toggle. */ + /** Select toggle. The toggle should either be a renderer function which forwards the given toggle ref, or a direct ReactNode that should be passed along with the toggleRef property. */ toggle: React.ReactNode | ((toggleRef: React.RefObject) => React.ReactNode); - /** Ref of the select toggle ReactNode. */ + /** Ref of the select toggle. This property should be passed when toggle is assigned a node, to enable built in basic interaction handling. */ toggleRef?: React.RefObject; /** Flag indicating the toggle should be focused after a selection. If this use case is too restrictive, the optional toggleRef property with a node toggle may be used to control focus. */ shouldFocusToggleOnSelect?: boolean; From 8ab9226c5844bbb2c52d25fab3d6e89ad044504c Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Wed, 17 May 2023 17:30:59 -0400 Subject: [PATCH 14/17] update wording --- packages/react-core/src/components/Select/Select.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-core/src/components/Select/Select.tsx b/packages/react-core/src/components/Select/Select.tsx index acf511239d5..cda472486dd 100644 --- a/packages/react-core/src/components/Select/Select.tsx +++ b/packages/react-core/src/components/Select/Select.tsx @@ -41,9 +41,9 @@ export interface SelectProps extends MenuProps, OUIAProps { /** Function callback when user selects an option. */ onSelect?: (event?: React.MouseEvent, itemId?: string | number) => void; /** Callback to allow the select component to change the open state of the menu. - * Triggered by clicking outside of the menu, or by pressing either tab or escape (or specificed in onOpenChangeKeys). */ + * Triggered by clicking outside of the menu, or by pressing any keys specificed in onOpenChangeKeys. */ onOpenChange?: (isOpen: boolean) => void; - /** @beta Keys that trigger onOpenChange, defaults to tab and escape. */ + /** @beta Keys that trigger onOpenChange, defaults to tab and escape. It is highly recommended to include Escape in the array, while Tab may be omitted if the menu contains non-menu items that are focusable. */ onOpenChangeKeys?: string[]; /** Indicates if the select should be without the outer box-shadow */ isPlain?: boolean; From dbdededf5ffff42c3835f40db35bc0e6da117047 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Wed, 17 May 2023 17:51:10 -0400 Subject: [PATCH 15/17] use single prop --- .../src/components/Dropdown/Dropdown.tsx | 12 ++++++++---- .../react-core/src/components/Select/Select.tsx | 14 ++++++++------ .../components/Select/examples/SelectViewMore.tsx | 3 +-- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/packages/react-core/src/components/Dropdown/Dropdown.tsx b/packages/react-core/src/components/Dropdown/Dropdown.tsx index 2b0a04d23d0..4f41dba8a27 100644 --- a/packages/react-core/src/components/Dropdown/Dropdown.tsx +++ b/packages/react-core/src/components/Dropdown/Dropdown.tsx @@ -28,7 +28,9 @@ export interface DropdownProps extends MenuProps, OUIAProps { /** Classes applied to root element of dropdown. */ className?: string; /** Dropdown toggle. The toggle should either be a renderer function which forwards the given toggle ref, or a direct ReactNode that should be passed along with the toggleRef property. */ - toggle: React.ReactNode | ((toggleRef: React.RefObject) => React.ReactNode); + toggle: + | { toggleNode: React.ReactNode; toggleRef: React.RefObject } + | ((toggleRef: React.RefObject) => React.ReactNode); /** Ref of the dropdown toggle. This property should be passed when toggle is assigned a node, to enable built in basic interaction handling. */ toggleRef?: React.RefObject; /** Flag to indicate if menu is opened.*/ @@ -62,7 +64,6 @@ const DropdownBase: React.FunctionComponent = ({ onSelect, isOpen, toggle, - toggleRef: toggleRefProp, shouldFocusToggleOnSelect = false, onOpenChange, isPlain, @@ -79,7 +80,10 @@ const DropdownBase: React.FunctionComponent = ({ const ouiaProps = useOUIAProps(Dropdown.displayName, ouiaId, ouiaSafe); const menuRef = (innerRef as React.RefObject) || localMenuRef; - const toggleRef = (toggleRefProp as React.RefObject) || localToggleRef; + const toggleRef = + typeof toggle === 'function' || (typeof toggle !== 'function' && !toggle.toggleRef) + ? localToggleRef + : (toggle?.toggleRef as React.RefObject); React.useEffect(() => { const handleMenuKeys = (event: KeyboardEvent) => { @@ -143,7 +147,7 @@ const DropdownBase: React.FunctionComponent = ({ ); return ( ) => React.ReactNode); - /** Ref of the select toggle. This property should be passed when toggle is assigned a node, to enable built in basic interaction handling. */ - toggleRef?: React.RefObject; + toggle: + | { toggleNode: React.ReactNode; toggleRef: React.RefObject } + | ((toggleRef: React.RefObject) => React.ReactNode); /** Flag indicating the toggle should be focused after a selection. If this use case is too restrictive, the optional toggleRef property with a node toggle may be used to control focus. */ shouldFocusToggleOnSelect?: boolean; /** Function callback when user selects an option. */ @@ -64,7 +64,6 @@ const SelectBase: React.FunctionComponent = ({ isOpen, selected, toggle, - toggleRef: toggleRefProp, shouldFocusToggleOnSelect = false, onOpenChange, onOpenChangeKeys = ['Escape', 'Tab'], @@ -79,7 +78,10 @@ const SelectBase: React.FunctionComponent = ({ const localToggleRef = React.useRef(); const menuRef = (innerRef as React.RefObject) || localMenuRef; - const toggleRef = (toggleRefProp as React.RefObject) || localToggleRef; + const toggleRef = + typeof toggle === 'function' || (typeof toggle !== 'function' && !toggle.toggleRef) + ? localToggleRef + : (toggle?.toggleRef as React.RefObject); React.useEffect(() => { const handleMenuKeys = (event: KeyboardEvent) => { @@ -146,7 +148,7 @@ const SelectBase: React.FunctionComponent = ({ ); return ( { selected={selected} onSelect={onSelect} onOpenChange={(isOpen) => setIsOpen(isOpen)} - toggle={toggle} - toggleRef={toggleRef} + toggle={{ toggleNode: toggle, toggleRef }} > {visibleOptions.map((option) => { From 1badf5263ccee0b47791e51b41c47085d4269c18 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Thu, 18 May 2023 00:04:37 -0400 Subject: [PATCH 16/17] move toggle object to interface, add to docs --- .../react-core/src/components/Dropdown/Dropdown.tsx | 11 ++++++++--- .../src/components/Dropdown/examples/Dropdown.md | 11 ++++++++++- packages/react-core/src/components/Select/Select.tsx | 11 ++++++++--- .../src/components/Select/examples/Select.md | 3 ++- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/packages/react-core/src/components/Dropdown/Dropdown.tsx b/packages/react-core/src/components/Dropdown/Dropdown.tsx index 4f41dba8a27..cfad7780cbc 100644 --- a/packages/react-core/src/components/Dropdown/Dropdown.tsx +++ b/packages/react-core/src/components/Dropdown/Dropdown.tsx @@ -19,6 +19,13 @@ export interface DropdownPopperProps { enableFlip?: boolean; } +export interface DropdownToggleProps { + /** Dropdown toggle node. */ + toggleNode: React.ReactNode; + /** Reference to the toggle. */ + toggleRef: React.RefObject; +} + /** * See the Menu documentation for additional props that may be passed. */ @@ -28,9 +35,7 @@ export interface DropdownProps extends MenuProps, OUIAProps { /** Classes applied to root element of dropdown. */ className?: string; /** Dropdown toggle. The toggle should either be a renderer function which forwards the given toggle ref, or a direct ReactNode that should be passed along with the toggleRef property. */ - toggle: - | { toggleNode: React.ReactNode; toggleRef: React.RefObject } - | ((toggleRef: React.RefObject) => React.ReactNode); + toggle: DropdownToggleProps | ((toggleRef: React.RefObject) => React.ReactNode); /** Ref of the dropdown toggle. This property should be passed when toggle is assigned a node, to enable built in basic interaction handling. */ toggleRef?: React.RefObject; /** Flag to indicate if menu is opened.*/ diff --git a/packages/react-core/src/components/Dropdown/examples/Dropdown.md b/packages/react-core/src/components/Dropdown/examples/Dropdown.md index cd28d0bf0d4..f3b7dc82cde 100644 --- a/packages/react-core/src/components/Dropdown/examples/Dropdown.md +++ b/packages/react-core/src/components/Dropdown/examples/Dropdown.md @@ -3,7 +3,16 @@ id: Dropdown section: components subsection: menus cssPrefix: pf-c-menu -propComponents: ['Dropdown', DropdownGroup, 'DropdownItem', 'DropdownList', 'MenuToggle', 'DropdownPopperProps'] +propComponents: + [ + 'Dropdown', + 'DropdownGroup', + 'DropdownItem', + 'DropdownList', + 'MenuToggle', + 'DropdownToggleProps', + 'DropdownPopperProps' + ] --- import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; diff --git a/packages/react-core/src/components/Select/Select.tsx b/packages/react-core/src/components/Select/Select.tsx index 07dd00bcff1..2a86b6a39a8 100644 --- a/packages/react-core/src/components/Select/Select.tsx +++ b/packages/react-core/src/components/Select/Select.tsx @@ -19,6 +19,13 @@ export interface SelectPopperProps { enableFlip?: boolean; } +export interface SelectToggleProps { + /** Select toggle node. */ + toggleNode: React.ReactNode; + /** Reference to the toggle. */ + toggleRef: React.RefObject; +} + /** * See the Menu documentation for additional props that may be passed. */ @@ -33,9 +40,7 @@ export interface SelectProps extends MenuProps, OUIAProps { /** Single itemId for single select menus, or array of itemIds for multi select. You can also specify isSelected on the SelectOption. */ selected?: any | any[]; /** Select toggle. The toggle should either be a renderer function which forwards the given toggle ref, or a direct ReactNode that should be passed along with the toggleRef property. */ - toggle: - | { toggleNode: React.ReactNode; toggleRef: React.RefObject } - | ((toggleRef: React.RefObject) => React.ReactNode); + toggle: SelectToggleProps | ((toggleRef: React.RefObject) => React.ReactNode); /** Flag indicating the toggle should be focused after a selection. If this use case is too restrictive, the optional toggleRef property with a node toggle may be used to control focus. */ shouldFocusToggleOnSelect?: boolean; /** Function callback when user selects an option. */ diff --git a/packages/react-core/src/components/Select/examples/Select.md b/packages/react-core/src/components/Select/examples/Select.md index 72842f4d19d..3ebabaca5ba 100644 --- a/packages/react-core/src/components/Select/examples/Select.md +++ b/packages/react-core/src/components/Select/examples/Select.md @@ -3,7 +3,8 @@ id: Select section: components subsection: menus cssPrefix: pf-c-select -propComponents: ['Select', 'SelectOption', 'SelectGroup', 'SelectList', 'MenuToggle', 'SelectPopperProps'] +propComponents: + ['Select', 'SelectOption', 'SelectGroup', 'SelectList', 'MenuToggle', 'SelectToggleProps', 'SelectPopperProps'] ouia: true --- From 47a46f02b005c7cc1e308c07a2e344c8109c9bed Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Thu, 18 May 2023 09:32:45 -0400 Subject: [PATCH 17/17] prop req and remove extra prop --- packages/react-core/src/components/Dropdown/Dropdown.tsx | 4 +--- packages/react-core/src/components/Select/Select.tsx | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/react-core/src/components/Dropdown/Dropdown.tsx b/packages/react-core/src/components/Dropdown/Dropdown.tsx index cfad7780cbc..f3d2b3b4d4a 100644 --- a/packages/react-core/src/components/Dropdown/Dropdown.tsx +++ b/packages/react-core/src/components/Dropdown/Dropdown.tsx @@ -23,7 +23,7 @@ export interface DropdownToggleProps { /** Dropdown toggle node. */ toggleNode: React.ReactNode; /** Reference to the toggle. */ - toggleRef: React.RefObject; + toggleRef?: React.RefObject; } /** @@ -36,8 +36,6 @@ export interface DropdownProps extends MenuProps, OUIAProps { className?: string; /** Dropdown toggle. The toggle should either be a renderer function which forwards the given toggle ref, or a direct ReactNode that should be passed along with the toggleRef property. */ toggle: DropdownToggleProps | ((toggleRef: React.RefObject) => React.ReactNode); - /** Ref of the dropdown toggle. This property should be passed when toggle is assigned a node, to enable built in basic interaction handling. */ - toggleRef?: React.RefObject; /** Flag to indicate if menu is opened.*/ isOpen?: boolean; /** Flag indicating the toggle should be focused after a selection. If this use case is too restrictive, the optional toggleRef property with a node toggle may be used to control focus. */ diff --git a/packages/react-core/src/components/Select/Select.tsx b/packages/react-core/src/components/Select/Select.tsx index 2a86b6a39a8..4a492d812d5 100644 --- a/packages/react-core/src/components/Select/Select.tsx +++ b/packages/react-core/src/components/Select/Select.tsx @@ -23,7 +23,7 @@ export interface SelectToggleProps { /** Select toggle node. */ toggleNode: React.ReactNode; /** Reference to the toggle. */ - toggleRef: React.RefObject; + toggleRef?: React.RefObject; } /**