From 91e9aa3cbbe160406eed6759de1a4210b3db3886 Mon Sep 17 00:00:00 2001 From: nicolethoen Date: Wed, 10 Sep 2025 10:10:54 -0400 Subject: [PATCH 1/2] feat(Select): introduce variant of Select using FloatingUI rather than Popper --- packages/react-core/package.json | 1 + .../src/components/Select/Select.tsx | 8 +- .../Select/examples/SelectBasic.tsx | 1 + .../Select/examples/SelectCheckbox.tsx | 1 + .../Select/examples/SelectFooter.tsx | 1 + .../Select/examples/SelectGrouped.tsx | 1 + .../Select/examples/SelectMultiTypeahead.tsx | 1 + .../examples/SelectMultiTypeaheadCheckbox.tsx | 1 + .../SelectMultiTypeaheadCreatable.tsx | 1 + .../examples/SelectOptionVariations.tsx | 1 + .../Select/examples/SelectTypeahead.tsx | 1 + .../examples/SelectTypeaheadCreatable.tsx | 1 + .../Select/examples/SelectValidated.tsx | 1 + .../Select/examples/SelectViewMore.tsx | 1 + .../helpers/FloatingUI/FloatingUIPopper.tsx | 508 +++++++++++++++++ .../__tests__/FloatingUIPopper.test.tsx | 509 ++++++++++++++++++ .../src/helpers/FloatingUI/index.ts | 1 + packages/react-core/src/helpers/index.ts | 1 + yarn.lock | 55 +- 19 files changed, 1093 insertions(+), 2 deletions(-) create mode 100644 packages/react-core/src/helpers/FloatingUI/FloatingUIPopper.tsx create mode 100644 packages/react-core/src/helpers/FloatingUI/__tests__/FloatingUIPopper.test.tsx create mode 100644 packages/react-core/src/helpers/FloatingUI/index.ts diff --git a/packages/react-core/package.json b/packages/react-core/package.json index 66f2bc7e16f..5c1a3c1076b 100644 --- a/packages/react-core/package.json +++ b/packages/react-core/package.json @@ -46,6 +46,7 @@ "subpaths": "node ../../scripts/exportSubpaths.mjs --config subpaths.config.json" }, "dependencies": { + "@floating-ui/react": "^0.26.0", "@patternfly/react-icons": "workspace:^", "@patternfly/react-styles": "workspace:^", "@patternfly/react-tokens": "workspace:^", diff --git a/packages/react-core/src/components/Select/Select.tsx b/packages/react-core/src/components/Select/Select.tsx index d42ec485eca..830aa26827f 100644 --- a/packages/react-core/src/components/Select/Select.tsx +++ b/packages/react-core/src/components/Select/Select.tsx @@ -2,6 +2,7 @@ import { forwardRef, useEffect, useRef } from 'react'; import { css } from '@patternfly/react-styles'; import { Menu, MenuContent, MenuProps } from '../Menu'; import { Popper } from '../../helpers/Popper/Popper'; +import { FloatingUIPopper } from '../../helpers/FloatingUI/FloatingUIPopper'; import { getOUIAProps, OUIAProps, getDefaultOUIAId, onToggleArrowKeydownDefault } from '../../helpers'; export interface SelectPopperProps { @@ -86,6 +87,8 @@ export interface SelectProps extends MenuProps, OUIAProps { shouldPreventScrollOnItemFocus?: boolean; /** Time in ms to wait before firing the toggles' focus event. Defaults to 0 */ focusTimeoutDelay?: number; + /** @beta Flag to use Floating UI instead of Popper for positioning. Defaults to false. */ + useFloatingUI?: boolean; } const SelectBase: React.FunctionComponent = ({ @@ -111,6 +114,7 @@ const SelectBase: React.FunctionComponent = ({ isScrollable, shouldPreventScrollOnItemFocus = true, focusTimeoutDelay = 0, + useFloatingUI = false, ...props }: SelectProps & OUIAProps) => { const localMenuRef = useRef(undefined); @@ -212,8 +216,10 @@ const SelectBase: React.FunctionComponent = ({ ); + const PopperComponent = useFloatingUI ? FloatingUIPopper : Popper; + return ( - { onOpenChange={(isOpen) => setIsOpen(isOpen)} toggle={toggle} shouldFocusToggleOnSelect + useFloatingUI > Option 1 diff --git a/packages/react-core/src/components/Select/examples/SelectCheckbox.tsx b/packages/react-core/src/components/Select/examples/SelectCheckbox.tsx index 2a1d582d83c..e5b9dd5f60d 100644 --- a/packages/react-core/src/components/Select/examples/SelectCheckbox.tsx +++ b/packages/react-core/src/components/Select/examples/SelectCheckbox.tsx @@ -45,6 +45,7 @@ export const SelectCheckbox: React.FunctionComponent = () => { onSelect={onSelect} onOpenChange={(nextOpen: boolean) => setIsOpen(nextOpen)} toggle={toggle} + useFloatingUI > diff --git a/packages/react-core/src/components/Select/examples/SelectFooter.tsx b/packages/react-core/src/components/Select/examples/SelectFooter.tsx index fa61dfd213a..f04102b3497 100644 --- a/packages/react-core/src/components/Select/examples/SelectFooter.tsx +++ b/packages/react-core/src/components/Select/examples/SelectFooter.tsx @@ -41,6 +41,7 @@ export const SelectFooter: React.FunctionComponent = () => { id="menu-with-footer" onSelect={onSelect} selected={selected} + useFloatingUI > Option 1 diff --git a/packages/react-core/src/components/Select/examples/SelectGrouped.tsx b/packages/react-core/src/components/Select/examples/SelectGrouped.tsx index 4a592822d78..6c460619151 100644 --- a/packages/react-core/src/components/Select/examples/SelectGrouped.tsx +++ b/packages/react-core/src/components/Select/examples/SelectGrouped.tsx @@ -49,6 +49,7 @@ export const SelectGrouped: React.FunctionComponent = () => { onOpenChange={(isOpen) => setIsOpen(isOpen)} toggle={toggle} shouldFocusToggleOnSelect + useFloatingUI > diff --git a/packages/react-core/src/components/Select/examples/SelectMultiTypeahead.tsx b/packages/react-core/src/components/Select/examples/SelectMultiTypeahead.tsx index 20095710a3d..cee0f85d13e 100644 --- a/packages/react-core/src/components/Select/examples/SelectMultiTypeahead.tsx +++ b/packages/react-core/src/components/Select/examples/SelectMultiTypeahead.tsx @@ -243,6 +243,7 @@ export const SelectMultiTypeahead: React.FunctionComponent = () => { }} toggle={toggle} variant="typeahead" + useFloatingUI > {selectOptions.map((option, index) => ( diff --git a/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx b/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx index 9241c38fa18..a74998d092b 100644 --- a/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx +++ b/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx @@ -237,6 +237,7 @@ export const SelectMultiTypeaheadCheckbox: React.FunctionComponent = () => { }} toggle={toggle} variant="typeahead" + useFloatingUI > {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 index e7e2be5428c..8e508c54faa 100644 --- a/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCreatable.tsx +++ b/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCreatable.tsx @@ -256,6 +256,7 @@ export const SelectMultiTypeaheadCreatable: React.FunctionComponent = () => { }} toggle={toggle} variant="typeahead" + useFloatingUI > {selectOptions.map((option, index) => ( diff --git a/packages/react-core/src/components/Select/examples/SelectOptionVariations.tsx b/packages/react-core/src/components/Select/examples/SelectOptionVariations.tsx index 94ee3cd4d7c..0a9ad2af1ff 100644 --- a/packages/react-core/src/components/Select/examples/SelectOptionVariations.tsx +++ b/packages/react-core/src/components/Select/examples/SelectOptionVariations.tsx @@ -42,6 +42,7 @@ export const SelectOptionVariations: React.FunctionComponent = () => { onOpenChange={(isOpen) => setIsOpen(isOpen)} toggle={toggle} shouldFocusToggleOnSelect + useFloatingUI > Basic option diff --git a/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx b/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx index b7a02d350ab..2f08c4097a9 100644 --- a/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx +++ b/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx @@ -240,6 +240,7 @@ export const SelectTypeahead: React.FunctionComponent = () => { }} toggle={toggle} variant="typeahead" + useFloatingUI > {selectOptions.map((option, index) => ( diff --git a/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx b/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx index 5d9ec246281..6f952f2bccc 100644 --- a/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx +++ b/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx @@ -247,6 +247,7 @@ export const SelectTypeaheadCreatable: React.FunctionComponent = () => { }} toggle={toggle} variant="typeahead" + useFloatingUI > {selectOptions.map((option, index) => ( diff --git a/packages/react-core/src/components/Select/examples/SelectValidated.tsx b/packages/react-core/src/components/Select/examples/SelectValidated.tsx index 68dfa2db495..0f6b5240af1 100644 --- a/packages/react-core/src/components/Select/examples/SelectValidated.tsx +++ b/packages/react-core/src/components/Select/examples/SelectValidated.tsx @@ -54,6 +54,7 @@ export const SelectValidated: React.FunctionComponent = () => { onOpenChange={(isOpen) => setIsOpen(isOpen)} toggle={toggle} shouldFocusToggleOnSelect + useFloatingUI > Success diff --git a/packages/react-core/src/components/Select/examples/SelectViewMore.tsx b/packages/react-core/src/components/Select/examples/SelectViewMore.tsx index b72a241f3ac..c1c858ff893 100644 --- a/packages/react-core/src/components/Select/examples/SelectViewMore.tsx +++ b/packages/react-core/src/components/Select/examples/SelectViewMore.tsx @@ -122,6 +122,7 @@ export const SelectViewMore: React.FunctionComponent = () => { onSelect={onSelect} onOpenChange={(isOpen) => setIsOpen(isOpen)} toggle={{ toggleNode: toggle, toggleRef }} + useFloatingUI > {visibleOptions.map((option) => { diff --git a/packages/react-core/src/helpers/FloatingUI/FloatingUIPopper.tsx b/packages/react-core/src/helpers/FloatingUI/FloatingUIPopper.tsx new file mode 100644 index 00000000000..d67aaef475a --- /dev/null +++ b/packages/react-core/src/helpers/FloatingUI/FloatingUIPopper.tsx @@ -0,0 +1,508 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { + useFloating, + autoUpdate, + offset, + flip, + shift, + size, + useClick, + useHover, + useFocus, + useDismiss, + useInteractions, + FloatingPortal, + Placement +} from '@floating-ui/react'; +// Inline utility functions to avoid import issues +const clearTimeouts = (timeoutRefs: React.MutableRefObject | null>[]) => { + timeoutRefs.forEach((timeoutRef) => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + }); +}; + +const getLanguageDirection = (targetElement: HTMLElement = document.body, defaultDirection: 'ltr' | 'rtl' = 'ltr') => { + if (!targetElement) { + return defaultDirection; + } + const computedStyle = window.getComputedStyle(targetElement); + const direction = computedStyle.direction; + return direction === 'rtl' ? 'rtl' : 'ltr'; +}; + +// Re-export the same interface as the current Popper for 100% compatibility +export interface FloatingUIPopperProps { + /** + * Trigger reference element to which the popper is relatively placed to. + */ + trigger?: React.ReactNode; + /** + * A reference to the trigger reference element that can be passed instead of or along + * with the trigger prop. When passed along with the trigger prop, the div element that + * wraps the trigger will be removed. + */ + triggerRef?: HTMLElement | (() => HTMLElement) | React.RefObject; + /** The popper (menu/tooltip/popover) element */ + popper: React.ReactElement; + /** + * Reference to the popper (menu/tooltip/popover) element. + * Passing this prop will remove the wrapper div element from the popper. + */ + _popperRef?: HTMLElement | (() => HTMLElement) | React.RefObject; + /** popper direction */ + direction?: 'up' | 'down'; + /** popper position */ + position?: 'right' | 'left' | 'center' | 'start' | 'end'; + /** Instead of direction and position can set the placement of the popper */ + placement?: Placement; + /** 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'; + /** Maximum width of the popper. If the value is "trigger", it will set the max width to the trigger element's width */ + maxWidth?: string | 'trigger'; + /** The container to append the popper to. Defaults to 'inline'. */ + appendTo?: HTMLElement | (() => HTMLElement) | 'inline'; + /** z-index of the popper element */ + zIndex?: number; + /** True to make the popper visible */ + isVisible?: boolean; + /** + * Map class names to positions, for example: + * { + * top: styles.modifiers.top, + * bottom: styles.modifiers.bottom, + * left: styles.modifiers.left, + * right: styles.modifiers.right + * } + */ + _positionModifiers?: { + top?: string; + right?: string; + bottom?: string; + left?: string; + topStart?: string; + topEnd?: string; + bottomStart?: string; + bottomEnd?: string; + leftStart?: string; + leftEnd?: string; + rightStart?: string; + rightEnd?: string; + }; + /** Distance of the popper to the trigger */ + distance?: number; + /** Callback function when mouse enters trigger */ + onMouseEnter?: (event?: MouseEvent) => void; + /** Callback function when mouse leaves trigger */ + onMouseLeave?: (event?: MouseEvent) => void; + /** Callback function when trigger is focused */ + onFocus?: (event?: FocusEvent) => void; + /** Callback function when trigger is blurred (focus leaves) */ + onBlur?: (event?: FocusEvent) => void; + /** Callback function when trigger is clicked */ + onTriggerClick?: (event?: MouseEvent) => void; + /** Callback function when Enter key is used on trigger */ + _onTriggerEnter?: (event?: KeyboardEvent) => void; + /** Callback function when popper is clicked */ + onPopperClick?: (event?: MouseEvent) => void; + /** Callback function when mouse enters popper content */ + onPopperMouseEnter?: (event?: MouseEvent) => void; + /** Callback function when mouse leaves popper content */ + onPopperMouseLeave?: (event?: MouseEvent) => void; + /** Callback function when document is clicked */ + _onDocumentClick?: (event?: MouseEvent, triggerElement?: HTMLElement, popperElement?: HTMLElement) => void; + /** Callback function when keydown event occurs on document */ + _onDocumentKeyDown?: (event?: KeyboardEvent) => void; + /** Enable to flip the popper when it reaches the boundary */ + enableFlip?: boolean; + /** The behavior of how the popper flips when it reaches the boundary */ + flipBehavior?: + | 'flip' + | ( + | 'top' + | 'bottom' + | 'left' + | 'right' + | 'top-start' + | 'top-end' + | 'bottom-start' + | 'bottom-end' + | 'left-start' + | 'left-end' + | 'right-start' + | 'right-end' + )[]; + /** The duration of the CSS fade transition animation. */ + animationDuration?: number; + /** Delay in ms before the popper appears */ + entryDelay?: number; + /** Delay in ms before the popper disappears */ + exitDelay?: number; + /** Callback when popper's hide transition has finished executing */ + onHidden?: () => void; + /** + * Lifecycle function invoked when the popper begins to transition out. + */ + onHide?: () => void; + /** + * Lifecycle function invoked when the popper has been mounted to the DOM. + */ + onMount?: () => void; + /** + * Lifecycle function invoked when the popper begins to transition in. + */ + onShow?: () => void; + /** + * Lifecycle function invoked when the popper has fully transitioned in. + */ + onShown?: () => void; + /** Flag to prevent the popper from overflowing its container and becoming partially obscured. */ + preventOverflow?: boolean; +} + +const getFloatingUIOpacityTransition = (animationDuration: number) => + `opacity ${animationDuration}ms cubic-bezier(.54, 1.5, .38, 1.11)`; + +export const FloatingUIPopper: React.FunctionComponent = ({ + trigger, + popper, + direction = 'down', + position = 'start', + placement, + width, + minWidth = 'trigger', + maxWidth, + appendTo = () => document.body, + zIndex = 9999, + isVisible = true, + _positionModifiers, + distance = 0, + onMouseEnter, + onMouseLeave, + onFocus, + onBlur, + _onDocumentClick, + onTriggerClick, + _onTriggerEnter, + onPopperClick, + onPopperMouseEnter, + onPopperMouseLeave, + _onDocumentKeyDown, + enableFlip = true, + flipBehavior = 'flip', + triggerRef, + _popperRef, + animationDuration = 0, + entryDelay = 0, + exitDelay = 0, + onHidden = () => {}, + onHide = () => {}, + onMount = () => {}, + onShow = () => {}, + onShown = () => {}, + preventOverflow = false +}) => { + const [opacity, setOpacity] = useState(0); + const [internalIsVisible, setInternalIsVisible] = useState(isVisible); + const transitionTimerRef = useRef | null>(null); + const showTimerRef = useRef | null>(null); + const hideTimerRef = useRef | null>(null); + const prevExitDelayRef = useRef(undefined); + + const showPopper = isVisible || internalIsVisible; + + // Get language direction for RTL support + const languageDirection = getLanguageDirection(document.body); + + // Handle position mapping with RTL support + const internalPosition = useMemo<'left' | 'right' | 'center'>(() => { + const fixedPositions = { left: 'left', right: 'right', center: 'center' }; + const positionMap = { + ltr: { + start: 'left', + end: 'right', + ...fixedPositions + }, + rtl: { + start: 'right', + end: 'left', + ...fixedPositions + } + }; + return positionMap[languageDirection][position] as 'left' | 'right' | 'center'; + }, [position, languageDirection]); + + // Convert Popper direction/position to Floating UI placement + const floatingUIPlacement = useMemo(() => { + if (placement) { + return placement; + } + + let convertedPlacement = direction === 'up' ? 'top' : 'bottom'; + + if (internalPosition !== 'center') { + convertedPlacement = `${convertedPlacement}-${internalPosition === 'right' ? 'end' : 'start'}`; + } + + return convertedPlacement as Placement; + }, [direction, placement, internalPosition]); + + // Configure middleware + const middleware = useMemo(() => { + const middlewareArray = [ + offset(distance), + size({ + apply: ({ elements, rects }) => { + const triggerWidth = rects.reference.width; + + // Apply width constraints with higher specificity + // Only set width if explicitly provided (fixed width) + if (width) { + elements.floating.style.setProperty( + 'width', + width === 'trigger' ? `${triggerWidth}px` : width, + 'important' + ); + } + + // Always set minWidth (default is 'trigger' which allows content expansion) + if (minWidth) { + elements.floating.style.setProperty( + 'min-width', + minWidth === 'trigger' ? `${triggerWidth}px` : minWidth, + 'important' + ); + } + + if (maxWidth) { + elements.floating.style.setProperty( + 'max-width', + maxWidth === 'trigger' ? `${triggerWidth}px` : maxWidth, + 'important' + ); + } + } + }) + ]; + + // Conditionally add flip middleware + if (enableFlip) { + middlewareArray.push( + flip({ + fallbackPlacements: flipBehavior === 'flip' ? undefined : (flipBehavior as Placement[]) + }) + ); + } + + // Conditionally add shift middleware + if (preventOverflow) { + middlewareArray.push(shift()); + } + + return middlewareArray; + }, [distance, enableFlip, flipBehavior, preventOverflow, width, minWidth, maxWidth]); + + // Set up reference element - use triggerRef if provided, otherwise use the wrapper div + const [refElement, setRefElement] = useState(null); + + useEffect(() => { + if (triggerRef) { + if ((triggerRef as React.RefObject).current) { + setRefElement((triggerRef as React.RefObject).current); + } else if (typeof triggerRef === 'function') { + setRefElement(triggerRef()); + } else { + setRefElement(triggerRef as HTMLElement); + } + } + }, [triggerRef]); + + const { refs, floatingStyles, context } = useFloating({ + placement: floatingUIPlacement, + middleware, + open: showPopper, + whileElementsMounted: autoUpdate + }); + + // Set the reference element when triggerRef is provided + useEffect(() => { + if (refElement && triggerRef) { + refs.setReference(refElement); + } + }, [refElement, triggerRef, refs]); + + // Interaction hooks for event handling + const click = useClick(context); + const hover = useHover(context); + const focus = useFocus(context); + const dismiss = useDismiss(context); + + const { getReferenceProps, getFloatingProps } = useInteractions([click, hover, focus, dismiss]); + + // Handle custom event callbacks + const handleMouseEnter = useCallback( + (event: React.MouseEvent) => { + onMouseEnter?.(event.nativeEvent); + }, + [onMouseEnter] + ); + + const handleMouseLeave = useCallback( + (event: React.MouseEvent) => { + onMouseLeave?.(event.nativeEvent); + }, + [onMouseLeave] + ); + + const handleFocus = useCallback( + (event: React.FocusEvent) => { + onFocus?.(event.nativeEvent); + }, + [onFocus] + ); + + const handleBlur = useCallback( + (event: React.FocusEvent) => { + onBlur?.(event.nativeEvent); + }, + [onBlur] + ); + + const handleTriggerClick = useCallback( + (event: React.MouseEvent) => { + onTriggerClick?.(event.nativeEvent); + }, + [onTriggerClick] + ); + + const handlePopperClick = useCallback( + (event: React.MouseEvent) => { + onPopperClick?.(event.nativeEvent); + }, + [onPopperClick] + ); + + const handlePopperMouseEnter = useCallback( + (event: React.MouseEvent) => { + onPopperMouseEnter?.(event.nativeEvent); + }, + [onPopperMouseEnter] + ); + + const handlePopperMouseLeave = useCallback( + (event: React.MouseEvent) => { + onPopperMouseLeave?.(event.nativeEvent); + }, + [onPopperMouseLeave] + ); + + // Mount callback + useEffect(() => { + onMount(); + }, [onMount]); + + // Cleanup timers on unmount + useEffect( + () => () => { + clearTimeouts([transitionTimerRef, hideTimerRef, showTimerRef]); + }, + [] + ); + + // Handle animation and delays + const show = useCallback(() => { + onShow(); + clearTimeouts([transitionTimerRef, hideTimerRef]); + showTimerRef.current = setTimeout(() => { + setInternalIsVisible(true); + setOpacity(1); + onShown(); + }, entryDelay); + }, [onShow, onShown, entryDelay]); + + const hide = useCallback(() => { + onHide(); + clearTimeouts([showTimerRef]); + hideTimerRef.current = setTimeout(() => { + setOpacity(0); + transitionTimerRef.current = setTimeout(() => { + setInternalIsVisible(false); + onHidden(); + }, animationDuration); + }, exitDelay); + }, [onHide, onHidden, exitDelay, animationDuration]); + + useEffect(() => { + if (isVisible) { + show(); + } else { + hide(); + } + }, [isVisible, show, hide]); + + // Handle exit delay changes + useEffect(() => { + if (prevExitDelayRef.current !== undefined && prevExitDelayRef.current < exitDelay) { + clearTimeouts([transitionTimerRef, hideTimerRef]); + hideTimerRef.current = setTimeout(() => { + transitionTimerRef.current = setTimeout(() => { + setInternalIsVisible(false); + }, animationDuration); + }, exitDelay); + } + prevExitDelayRef.current = exitDelay; + }, [exitDelay, animationDuration]); + + // Determine the portal root element + let portalRoot = null; + if (appendTo !== 'inline') { + if (typeof appendTo === 'function') { + portalRoot = appendTo(); + } else { + portalRoot = appendTo; + } + } + + return ( + <> + {trigger && !triggerRef && ( +
+ {trigger} +
+ )} + {internalIsVisible && ( + +
+ {popper} +
+
+ )} + + ); +}; + +FloatingUIPopper.displayName = 'FloatingUIPopper'; diff --git a/packages/react-core/src/helpers/FloatingUI/__tests__/FloatingUIPopper.test.tsx b/packages/react-core/src/helpers/FloatingUI/__tests__/FloatingUIPopper.test.tsx new file mode 100644 index 00000000000..4c546c451f3 --- /dev/null +++ b/packages/react-core/src/helpers/FloatingUI/__tests__/FloatingUIPopper.test.tsx @@ -0,0 +1,509 @@ +import { ReactNode, createRef } from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { FloatingUIPopper } from '../FloatingUIPopper'; + +// Get the mock setReference function +const mockSetReference = jest.fn(); + +// Mock FloatingUI hooks +jest.mock('@floating-ui/react', () => ({ + useFloating: jest.fn(() => ({ + refs: { + setReference: mockSetReference, + setFloating: jest.fn() + }, + floatingStyles: { + position: 'absolute', + top: 0, + left: 0, + transform: 'translate3d(0px, 40px, 0px)' + }, + context: {}, + placement: 'bottom-start' + })), + autoUpdate: jest.fn(), + offset: jest.fn(), + flip: jest.fn(), + shift: jest.fn(), + size: jest.fn(), + useClick: jest.fn(() => ({})), + useHover: jest.fn(() => ({})), + useFocus: jest.fn(() => ({})), + useDismiss: jest.fn(() => ({})), + useInteractions: jest.fn(() => ({ + getReferenceProps: jest.fn(() => ({})), + getFloatingProps: jest.fn(() => ({})) + })), + FloatingPortal: ({ children }: { children: ReactNode }) =>
{children}
+})); + +// Mock the actual FloatingUI functions to capture their calls +const mockUseFloating = require('@floating-ui/react').useFloating; +const mockOffset = require('@floating-ui/react').offset; +const mockFlip = require('@floating-ui/react').flip; +const mockShift = require('@floating-ui/react').shift; +const mockSize = require('@floating-ui/react').size; + +describe('FloatingUIPopper', () => { + const defaultProps = { + trigger: , + popper:
Test Popper
, + isVisible: true + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('Basic Rendering', () => { + it('renders trigger and popper when visible', () => { + render(); + + expect(screen.getByText('Test Trigger')).toBeInTheDocument(); + expect(screen.getByText('Test Popper')).toBeInTheDocument(); + }); + + it('does not render popper when not visible', () => { + render(); + + expect(screen.getByText('Test Trigger')).toBeInTheDocument(); + expect(screen.queryByText('Test Popper')).not.toBeInTheDocument(); + }); + + it('renders without trigger when triggerRef is provided', () => { + const triggerRef = createRef(); + const mockElement = document.createElement('button'); + (triggerRef as any).current = mockElement; + + render(); + + // Should not render the wrapper div when triggerRef is provided + expect(screen.queryByText('Test Trigger')).not.toBeInTheDocument(); + }); + }); + + describe('Width Props Mapping', () => { + it('applies width prop correctly', () => { + render(); + + // Verify size middleware was called with correct parameters + expect(mockSize).toHaveBeenCalledWith( + expect.objectContaining({ + apply: expect.any(Function) + }) + ); + }); + + it('applies minWidth prop correctly', () => { + render(); + + expect(mockSize).toHaveBeenCalledWith( + expect.objectContaining({ + apply: expect.any(Function) + }) + ); + }); + + it('applies maxWidth prop correctly', () => { + render(); + + expect(mockSize).toHaveBeenCalledWith( + expect.objectContaining({ + apply: expect.any(Function) + }) + ); + }); + + it('handles trigger width values correctly', () => { + render(); + + expect(mockSize).toHaveBeenCalledWith( + expect.objectContaining({ + apply: expect.any(Function) + }) + ); + }); + + it('applies width constraints with important flag', () => { + render(); + + // Verify that size middleware was called + expect(mockSize).toHaveBeenCalled(); + + // Get the size middleware apply function + const sizeMiddleware = mockSize.mock.calls[0][0]; + const mockElements = { + floating: { + style: { + setProperty: jest.fn() + } + } + }; + const mockRects = { + reference: { width: 200 } + }; + + sizeMiddleware.apply({ elements: mockElements, rects: mockRects }); + + expect(mockElements.floating.style.setProperty).toHaveBeenCalledWith('min-width', '200px', 'important'); + }); + }); + + describe('Positioning Props Mapping', () => { + it('maps direction and position to correct placement', () => { + render(); + + expect(mockUseFloating).toHaveBeenCalledWith( + expect.objectContaining({ + placement: 'top-end' + }) + ); + }); + + it('maps direction and position to correct placement for center', () => { + render(); + + expect(mockUseFloating).toHaveBeenCalledWith( + expect.objectContaining({ + placement: 'bottom' + }) + ); + }); + + it('uses placement prop directly when provided', () => { + render(); + + expect(mockUseFloating).toHaveBeenCalledWith( + expect.objectContaining({ + placement: 'top-start' + }) + ); + }); + + it('handles RTL direction correctly', () => { + // Mock getLanguageDirection to return RTL + const originalGetComputedStyle = window.getComputedStyle; + window.getComputedStyle = jest.fn(() => ({ + direction: 'rtl' + })) as any; + + render(); + + expect(mockUseFloating).toHaveBeenCalledWith( + expect.objectContaining({ + placement: 'bottom-end' // start becomes end in RTL + }) + ); + + // Restore original function + window.getComputedStyle = originalGetComputedStyle; + }); + }); + + describe('Middleware Props Mapping', () => { + it('applies offset middleware with correct distance', () => { + render(); + + expect(mockOffset).toHaveBeenCalledWith(25); + }); + + it('applies flip middleware when enableFlip is true', () => { + render(); + + expect(mockFlip).toHaveBeenCalledWith( + expect.objectContaining({ + fallbackPlacements: undefined + }) + ); + }); + + it('applies flip middleware with custom flipBehavior', () => { + const customFlipBehavior = ['top', 'bottom'] as ('top' | 'bottom')[]; + render(); + + expect(mockFlip).toHaveBeenCalledWith( + expect.objectContaining({ + fallbackPlacements: customFlipBehavior + }) + ); + }); + + it('does not apply flip middleware when enableFlip is false', () => { + render(); + + expect(mockFlip).not.toHaveBeenCalled(); + }); + + it('applies shift middleware when preventOverflow is true', () => { + render(); + + expect(mockShift).toHaveBeenCalled(); + }); + + it('does not apply shift middleware when preventOverflow is false', () => { + render(); + + expect(mockShift).not.toHaveBeenCalled(); + }); + }); + + describe('Event Handler Props', () => { + it('calls onMouseEnter when provided', () => { + const onMouseEnter = jest.fn(); + render(); + + const trigger = screen.getByText('Test Trigger'); + fireEvent.mouseEnter(trigger); + + expect(onMouseEnter).toHaveBeenCalled(); + }); + + it('calls onMouseLeave when provided', () => { + const onMouseLeave = jest.fn(); + render(); + + const trigger = screen.getByText('Test Trigger'); + fireEvent.mouseLeave(trigger); + + expect(onMouseLeave).toHaveBeenCalled(); + }); + + it('calls onFocus when provided', () => { + const onFocus = jest.fn(); + render(); + + const trigger = screen.getByText('Test Trigger'); + fireEvent.focus(trigger); + + expect(onFocus).toHaveBeenCalled(); + }); + + it('calls onBlur when provided', () => { + const onBlur = jest.fn(); + render(); + + const trigger = screen.getByText('Test Trigger'); + fireEvent.blur(trigger); + + expect(onBlur).toHaveBeenCalled(); + }); + + it('calls onTriggerClick when provided', () => { + const onTriggerClick = jest.fn(); + render(); + + const trigger = screen.getByText('Test Trigger'); + fireEvent.click(trigger); + + expect(onTriggerClick).toHaveBeenCalled(); + }); + }); + + describe('Portal and Container Props', () => { + it('renders in FloatingPortal by default', () => { + render(); + + expect(screen.getByTestId('floating-portal')).toBeInTheDocument(); + }); + + it('applies correct zIndex', () => { + render(); + + const popper = screen.getByText('Test Popper').parentElement; + expect(popper).toHaveStyle({ zIndex: 5000 }); + }); + }); + + describe('Animation and Timing Props', () => { + it('applies animation duration correctly', () => { + render(); + + const popper = screen.getByText('Test Popper').parentElement; + expect(popper).toHaveStyle({ + transition: 'opacity 500ms cubic-bezier(.54, 1.5, .38, 1.11)' + }); + }); + + it('handles entry and exit delays', async () => { + jest.useFakeTimers(); + + render(); + + // Should not be visible initially + expect(screen.queryByText('Test Popper')).not.toBeInTheDocument(); + + // Fast forward timers to test delay behavior + jest.advanceTimersByTime(100); + + jest.useRealTimers(); + }); + }); + + describe('Lifecycle Callbacks', () => { + it('calls onMount when component mounts', () => { + const onMount = jest.fn(); + render(); + + expect(onMount).toHaveBeenCalled(); + }); + + it('calls onShow when becoming visible', () => { + const onShow = jest.fn(); + const { rerender } = render(); + + rerender(); + + expect(onShow).toHaveBeenCalled(); + }); + + it('calls onHide when becoming hidden', () => { + const onHide = jest.fn(); + const { rerender } = render(); + + rerender(); + + expect(onHide).toHaveBeenCalled(); + }); + }); + + describe('API Compatibility', () => { + it('maintains same prop interface as legacy Popper', () => { + // This test ensures that all props from the legacy Popper are supported + const allProps = { + trigger: , + popper:
Test
, + direction: 'down' as const, + position: 'start' as const, + placement: 'bottom-start' as const, + width: '200px', + minWidth: 'trigger', + maxWidth: '300px', + appendTo: document.body, + zIndex: 9999, + isVisible: true, + distance: 10, + onMouseEnter: jest.fn(), + onMouseLeave: jest.fn(), + onFocus: jest.fn(), + onBlur: jest.fn(), + onTriggerClick: jest.fn(), + onTriggerEnter: jest.fn(), + onPopperClick: jest.fn(), + onPopperMouseEnter: jest.fn(), + onPopperMouseLeave: jest.fn(), + onDocumentClick: jest.fn(), + onDocumentKeyDown: jest.fn(), + enableFlip: true, + flipBehavior: 'flip' as const, + triggerRef: createRef(), + popperRef: createRef(), + animationDuration: 300, + entryDelay: 0, + exitDelay: 0, + onHidden: jest.fn(), + onHide: jest.fn(), + onMount: jest.fn(), + onShow: jest.fn(), + onShown: jest.fn(), + preventOverflow: false + }; + + // Should render without errors + expect(() => render()).not.toThrow(); + }); + + it('handles undefined props gracefully', () => { + const minimalProps = { + trigger: , + popper:
Test
+ }; + + expect(() => render()).not.toThrow(); + }); + }); + + describe('Version Compatibility', () => { + it('should maintain core API compatibility across FloatingUI versions', () => { + // This test ensures that core FloatingUI APIs still work + expect(mockUseFloating).toBeDefined(); + expect(mockOffset).toBeDefined(); + expect(mockFlip).toBeDefined(); + expect(mockShift).toBeDefined(); + expect(mockSize).toBeDefined(); + }); + + it('should handle all legacy Popper props without errors', () => { + const allLegacyProps = { + trigger: , + popper:
Test
, + direction: 'down' as const, + position: 'start' as const, + placement: 'bottom-start' as const, + width: '200px', + minWidth: 'trigger', + maxWidth: '300px', + appendTo: document.body, + zIndex: 9999, + isVisible: true, + distance: 10, + enableFlip: true, + flipBehavior: 'flip' as const, + preventOverflow: false, + animationDuration: 300, + entryDelay: 0, + exitDelay: 0 + }; + + expect(() => render()).not.toThrow(); + }); + + it('should maintain width behavior compatibility', () => { + // Test that width props still work correctly + render(); + expect(mockSize).toHaveBeenCalled(); + }); + + it('should maintain positioning behavior compatibility', () => { + // Test that positioning props still work correctly + render(); + expect(mockUseFloating).toHaveBeenCalledWith( + expect.objectContaining({ + placement: 'top-end' + }) + ); + }); + }); + + describe('Reference Element Handling', () => { + it('uses triggerRef as reference element when provided', () => { + const triggerRef = createRef(); + const mockElement = document.createElement('button'); + (triggerRef as any).current = mockElement; + + render(); + + // Should call setReference with the element from triggerRef + expect(mockSetReference).toHaveBeenCalledWith(mockElement); + }); + + it('handles function triggerRef', () => { + const mockElement = document.createElement('button'); + const triggerRef = () => mockElement; + + render(); + + // Should call setReference with the element from triggerRef + expect(mockSetReference).toHaveBeenCalledWith(mockElement); + }); + + it('handles HTMLElement triggerRef', () => { + const mockElement = document.createElement('button'); + + render(); + + // Should call setReference with the element from triggerRef + expect(mockSetReference).toHaveBeenCalledWith(mockElement); + }); + }); +}); diff --git a/packages/react-core/src/helpers/FloatingUI/index.ts b/packages/react-core/src/helpers/FloatingUI/index.ts new file mode 100644 index 00000000000..121c0f58d50 --- /dev/null +++ b/packages/react-core/src/helpers/FloatingUI/index.ts @@ -0,0 +1 @@ +export * from './FloatingUIPopper'; diff --git a/packages/react-core/src/helpers/index.ts b/packages/react-core/src/helpers/index.ts index c2aee2b6820..a6dc3e7c361 100644 --- a/packages/react-core/src/helpers/index.ts +++ b/packages/react-core/src/helpers/index.ts @@ -6,6 +6,7 @@ export * from './typeUtils'; export * from './OUIA/ouia'; export * from './util'; export * from './Popper/Popper'; +export * from './FloatingUI/FloatingUIPopper'; export * from './useIsomorphicLayout'; export * from './KeyboardHandler'; export * from './resizeObserver'; diff --git a/yarn.lock b/yarn.lock index 7b5a2518574..716432f861e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2451,6 +2451,58 @@ __metadata: languageName: node linkType: hard +"@floating-ui/core@npm:^1.7.3": + version: 1.7.3 + resolution: "@floating-ui/core@npm:1.7.3" + dependencies: + "@floating-ui/utils": "npm:^0.2.10" + checksum: 10c0/edfc23800122d81df0df0fb780b7328ae6c5f00efbb55bd48ea340f4af8c5b3b121ceb4bb81220966ab0f87b443204d37105abdd93d94846468be3243984144c + languageName: node + linkType: hard + +"@floating-ui/dom@npm:^1.7.4": + version: 1.7.4 + resolution: "@floating-ui/dom@npm:1.7.4" + dependencies: + "@floating-ui/core": "npm:^1.7.3" + "@floating-ui/utils": "npm:^0.2.10" + checksum: 10c0/da6166c25f9b0729caa9f498685a73a0e28251613b35d27db8de8014bc9d045158a23c092b405321a3d67c2064909b6e2a7e6c1c9cc0f62967dca5779f5aef30 + languageName: node + linkType: hard + +"@floating-ui/react-dom@npm:^2.1.2": + version: 2.1.6 + resolution: "@floating-ui/react-dom@npm:2.1.6" + dependencies: + "@floating-ui/dom": "npm:^1.7.4" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 10c0/6654834a8e73ecbdbc6cad2ad8f7abc698ac7c1800ded4d61113525c591c03d2e3b59d3cf9205859221465ea38c87af4f9e6e204703c5b7a7e85332d1eef2e18 + languageName: node + linkType: hard + +"@floating-ui/react@npm:^0.26.0": + version: 0.26.28 + resolution: "@floating-ui/react@npm:0.26.28" + dependencies: + "@floating-ui/react-dom": "npm:^2.1.2" + "@floating-ui/utils": "npm:^0.2.8" + tabbable: "npm:^6.0.0" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 10c0/a42df129e1e976fe8ba3f4c8efdda265a0196c1b66b83f2b9b27423d08dcc765406f893aeff9d830e70e3f14a9d4c490867eb4c32983317cbaa33863b0fae6f6 + languageName: node + linkType: hard + +"@floating-ui/utils@npm:^0.2.10, @floating-ui/utils@npm:^0.2.8": + version: 0.2.10 + resolution: "@floating-ui/utils@npm:0.2.10" + checksum: 10c0/e9bc2a1730ede1ee25843937e911ab6e846a733a4488623cd353f94721b05ec2c9ec6437613a2ac9379a94c2fd40c797a2ba6fa1df2716f5ce4aa6ddb1cf9ea4 + languageName: node + linkType: hard + "@fortawesome/fontawesome-common-types@npm:^0.2.36": version: 0.2.36 resolution: "@fortawesome/fontawesome-common-types@npm:0.2.36" @@ -4190,6 +4242,7 @@ __metadata: version: 0.0.0-use.local resolution: "@patternfly/react-core@workspace:packages/react-core" dependencies: + "@floating-ui/react": "npm:^0.26.0" "@patternfly/patternfly": "npm:6.3.0-prerelease.55" "@patternfly/react-icons": "workspace:^" "@patternfly/react-styles": "workspace:^" @@ -21622,7 +21675,7 @@ __metadata: languageName: node linkType: hard -"tabbable@npm:^6.2.0": +"tabbable@npm:^6.0.0, tabbable@npm:^6.2.0": version: 6.2.0 resolution: "tabbable@npm:6.2.0" checksum: 10c0/ced8b38f05f2de62cd46836d77c2646c42b8c9713f5bd265daf0e78ff5ac73d3ba48a7ca45f348bafeef29b23da7187c72250742d37627883ef89cbd7fa76898 From 4a4d204bfb5505f2fef9017f526aeeba7b5b8ba3 Mon Sep 17 00:00:00 2001 From: nicolethoen Date: Thu, 11 Sep 2025 08:29:54 -0400 Subject: [PATCH 2/2] update comments --- packages/react-core/src/components/Select/Select.tsx | 2 +- .../react-core/src/components/Select/examples/SelectBasic.tsx | 2 +- .../src/components/Select/examples/SelectCheckbox.tsx | 2 +- .../react-core/src/components/Select/examples/SelectFooter.tsx | 2 +- .../react-core/src/components/Select/examples/SelectGrouped.tsx | 2 +- .../src/components/Select/examples/SelectMultiTypeahead.tsx | 2 +- .../components/Select/examples/SelectMultiTypeaheadCheckbox.tsx | 2 +- .../Select/examples/SelectMultiTypeaheadCreatable.tsx | 2 +- .../src/components/Select/examples/SelectOptionVariations.tsx | 2 +- .../src/components/Select/examples/SelectTypeahead.tsx | 2 +- .../src/components/Select/examples/SelectTypeaheadCreatable.tsx | 2 +- .../src/components/Select/examples/SelectValidated.tsx | 2 +- .../src/components/Select/examples/SelectViewMore.tsx | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/react-core/src/components/Select/Select.tsx b/packages/react-core/src/components/Select/Select.tsx index 830aa26827f..b76c6afb819 100644 --- a/packages/react-core/src/components/Select/Select.tsx +++ b/packages/react-core/src/components/Select/Select.tsx @@ -87,7 +87,7 @@ export interface SelectProps extends MenuProps, OUIAProps { shouldPreventScrollOnItemFocus?: boolean; /** Time in ms to wait before firing the toggles' focus event. Defaults to 0 */ focusTimeoutDelay?: number; - /** @beta Flag to use Floating UI instead of Popper for positioning. Defaults to false. */ + /** @beta Flag to use Floating UI instead of Popper for positioning. Defaults to false. If you're seeing positioning issues with the default Select component, try setting this flag to true. */ useFloatingUI?: boolean; } diff --git a/packages/react-core/src/components/Select/examples/SelectBasic.tsx b/packages/react-core/src/components/Select/examples/SelectBasic.tsx index c1f7f435db7..71a8ceb7c60 100644 --- a/packages/react-core/src/components/Select/examples/SelectBasic.tsx +++ b/packages/react-core/src/components/Select/examples/SelectBasic.tsx @@ -51,7 +51,7 @@ export const SelectBasic: React.FunctionComponent = () => { onOpenChange={(isOpen) => setIsOpen(isOpen)} toggle={toggle} shouldFocusToggleOnSelect - useFloatingUI + useFloatingUI // FloatingUI is recommended for better positioning. Will become the default in future breaking change. > Option 1 diff --git a/packages/react-core/src/components/Select/examples/SelectCheckbox.tsx b/packages/react-core/src/components/Select/examples/SelectCheckbox.tsx index e5b9dd5f60d..9b0cadeef42 100644 --- a/packages/react-core/src/components/Select/examples/SelectCheckbox.tsx +++ b/packages/react-core/src/components/Select/examples/SelectCheckbox.tsx @@ -45,7 +45,7 @@ export const SelectCheckbox: React.FunctionComponent = () => { onSelect={onSelect} onOpenChange={(nextOpen: boolean) => setIsOpen(nextOpen)} toggle={toggle} - useFloatingUI + useFloatingUI // FloatingUI is recommended for better positioning. Will become the default in future breaking change. > diff --git a/packages/react-core/src/components/Select/examples/SelectFooter.tsx b/packages/react-core/src/components/Select/examples/SelectFooter.tsx index f04102b3497..e658d809c96 100644 --- a/packages/react-core/src/components/Select/examples/SelectFooter.tsx +++ b/packages/react-core/src/components/Select/examples/SelectFooter.tsx @@ -41,7 +41,7 @@ export const SelectFooter: React.FunctionComponent = () => { id="menu-with-footer" onSelect={onSelect} selected={selected} - useFloatingUI + useFloatingUI // FloatingUI is recommended for better positioning. Will become the default in future breaking change. > Option 1 diff --git a/packages/react-core/src/components/Select/examples/SelectGrouped.tsx b/packages/react-core/src/components/Select/examples/SelectGrouped.tsx index 6c460619151..ac40bea4e7a 100644 --- a/packages/react-core/src/components/Select/examples/SelectGrouped.tsx +++ b/packages/react-core/src/components/Select/examples/SelectGrouped.tsx @@ -49,7 +49,7 @@ export const SelectGrouped: React.FunctionComponent = () => { onOpenChange={(isOpen) => setIsOpen(isOpen)} toggle={toggle} shouldFocusToggleOnSelect - useFloatingUI + useFloatingUI // FloatingUI is recommended for better positioning. Will become the default in future breaking change. > diff --git a/packages/react-core/src/components/Select/examples/SelectMultiTypeahead.tsx b/packages/react-core/src/components/Select/examples/SelectMultiTypeahead.tsx index cee0f85d13e..8d86c8deda4 100644 --- a/packages/react-core/src/components/Select/examples/SelectMultiTypeahead.tsx +++ b/packages/react-core/src/components/Select/examples/SelectMultiTypeahead.tsx @@ -243,7 +243,7 @@ export const SelectMultiTypeahead: React.FunctionComponent = () => { }} toggle={toggle} variant="typeahead" - useFloatingUI + useFloatingUI // FloatingUI is recommended for better positioning. Will become the default in future breaking change. > {selectOptions.map((option, index) => ( diff --git a/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx b/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx index a74998d092b..02504dea738 100644 --- a/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx +++ b/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx @@ -237,7 +237,7 @@ export const SelectMultiTypeaheadCheckbox: React.FunctionComponent = () => { }} toggle={toggle} variant="typeahead" - useFloatingUI + useFloatingUI // FloatingUI is recommended for better positioning. Will become the default in future breaking change. > {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 index 8e508c54faa..f9e16442078 100644 --- a/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCreatable.tsx +++ b/packages/react-core/src/components/Select/examples/SelectMultiTypeaheadCreatable.tsx @@ -256,7 +256,7 @@ export const SelectMultiTypeaheadCreatable: React.FunctionComponent = () => { }} toggle={toggle} variant="typeahead" - useFloatingUI + useFloatingUI // FloatingUI is recommended for better positioning. Will become the default in future breaking change. > {selectOptions.map((option, index) => ( diff --git a/packages/react-core/src/components/Select/examples/SelectOptionVariations.tsx b/packages/react-core/src/components/Select/examples/SelectOptionVariations.tsx index 0a9ad2af1ff..1a40fca1b20 100644 --- a/packages/react-core/src/components/Select/examples/SelectOptionVariations.tsx +++ b/packages/react-core/src/components/Select/examples/SelectOptionVariations.tsx @@ -42,7 +42,7 @@ export const SelectOptionVariations: React.FunctionComponent = () => { onOpenChange={(isOpen) => setIsOpen(isOpen)} toggle={toggle} shouldFocusToggleOnSelect - useFloatingUI + useFloatingUI // FloatingUI is recommended for better positioning. Will become the default in future breaking change. > Basic option diff --git a/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx b/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx index 2f08c4097a9..c81b85aec3b 100644 --- a/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx +++ b/packages/react-core/src/components/Select/examples/SelectTypeahead.tsx @@ -240,7 +240,7 @@ export const SelectTypeahead: React.FunctionComponent = () => { }} toggle={toggle} variant="typeahead" - useFloatingUI + useFloatingUI // FloatingUI is recommended for better positioning. Will become the default in future breaking change. > {selectOptions.map((option, index) => ( diff --git a/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx b/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx index 6f952f2bccc..73c60825c04 100644 --- a/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx +++ b/packages/react-core/src/components/Select/examples/SelectTypeaheadCreatable.tsx @@ -247,7 +247,7 @@ export const SelectTypeaheadCreatable: React.FunctionComponent = () => { }} toggle={toggle} variant="typeahead" - useFloatingUI + useFloatingUI // FloatingUI is recommended for better positioning. Will become the default in future breaking change. > {selectOptions.map((option, index) => ( diff --git a/packages/react-core/src/components/Select/examples/SelectValidated.tsx b/packages/react-core/src/components/Select/examples/SelectValidated.tsx index 0f6b5240af1..d401dcc5829 100644 --- a/packages/react-core/src/components/Select/examples/SelectValidated.tsx +++ b/packages/react-core/src/components/Select/examples/SelectValidated.tsx @@ -54,7 +54,7 @@ export const SelectValidated: React.FunctionComponent = () => { onOpenChange={(isOpen) => setIsOpen(isOpen)} toggle={toggle} shouldFocusToggleOnSelect - useFloatingUI + useFloatingUI // FloatingUI is recommended for better positioning. Will become the default in future breaking change. > Success diff --git a/packages/react-core/src/components/Select/examples/SelectViewMore.tsx b/packages/react-core/src/components/Select/examples/SelectViewMore.tsx index c1c858ff893..859f7bb589e 100644 --- a/packages/react-core/src/components/Select/examples/SelectViewMore.tsx +++ b/packages/react-core/src/components/Select/examples/SelectViewMore.tsx @@ -122,7 +122,7 @@ export const SelectViewMore: React.FunctionComponent = () => { onSelect={onSelect} onOpenChange={(isOpen) => setIsOpen(isOpen)} toggle={{ toggleNode: toggle, toggleRef }} - useFloatingUI + useFloatingUI // FloatingUI is recommended for better positioning. Will become the default in future breaking change. > {visibleOptions.map((option) => {