diff --git a/packages/feature-flags/feature-flags.yml b/packages/feature-flags/feature-flags.yml index 209458fc544b..078d8595cef1 100644 --- a/packages/feature-flags/feature-flags.yml +++ b/packages/feature-flags/feature-flags.yml @@ -45,3 +45,8 @@ feature-flags: description: > Enable the new focus wrap behavior that doesn't use sentinel nodes enabled: false + - name: enable-v12-dynamic-floating-styles + description: > + Enable dynamic setting of floating styles for components like Popover, + Tooltip, etc. + enabled: false diff --git a/packages/react/src/components/ComboBox/ComboBox.tsx b/packages/react/src/components/ComboBox/ComboBox.tsx index 9c7fdfb15a7c..38fb2ace9ebd 100644 --- a/packages/react/src/components/ComboBox/ComboBox.tsx +++ b/packages/react/src/components/ComboBox/ComboBox.tsx @@ -48,6 +48,7 @@ import { FormContext } from '../FluidForm'; import { useFloating, flip, autoUpdate } from '@floating-ui/react'; import { hide } from '@floating-ui/dom'; import { TranslateWithId } from '../../types/common'; +import { useFeatureFlag } from '../FeatureFlags'; const { InputBlur, @@ -417,12 +418,16 @@ const ComboBox = forwardRef( slug, ...rest } = props; + + const enableFloatingStyles = + useFeatureFlag('enable-v12-dynamic-floating-styles') || autoAlign; + const { refs, floatingStyles, middlewareData } = useFloating( - autoAlign + enableFloatingStyles ? { placement: direction, strategy: 'fixed', - middleware: [flip(), hide()], + middleware: autoAlign ? [flip(), hide()] : undefined, whileElementsMounted: autoUpdate, } : {} @@ -430,7 +435,7 @@ const ComboBox = forwardRef( const parentWidth = (refs?.reference?.current as HTMLElement)?.clientWidth; useEffect(() => { - if (autoAlign) { + if (enableFloatingStyles) { const updatedFloatingStyles = { ...floatingStyles, visibility: middlewareData.hide?.referenceHidden @@ -446,7 +451,7 @@ const ComboBox = forwardRef( refs.floating.current.style.width = parentWidth + 'px'; } } - }, [autoAlign, floatingStyles, refs.floating, parentWidth]); + }, [enableFloatingStyles, floatingStyles, refs.floating, parentWidth]); const [inputValue, setInputValue] = useState( getInputValue({ @@ -668,7 +673,7 @@ const ComboBox = forwardRef( [`${prefix}--list-box--up`]: direction === 'top', [`${prefix}--combo-box--warning`]: showWarning, [`${prefix}--combo-box--readonly`]: readOnly, - [`${prefix}--autoalign`]: autoAlign, + [`${prefix}--autoalign`]: enableFloatingStyles, }); const titleClasses = cx(`${prefix}--label`, { @@ -851,10 +856,10 @@ const ComboBox = forwardRef( const menuProps = useMemo( () => getMenuProps({ - ref: autoAlign ? refs.setFloating : null, + ref: enableFloatingStyles ? refs.setFloating : null, }), [ - autoAlign, + enableFloatingStyles, deprecatedAriaLabel, ariaLabel, getMenuProps, @@ -892,7 +897,7 @@ const ComboBox = forwardRef( light={light} size={size} warn={warn} - ref={autoAlign ? refs.setReference : null} + ref={enableFloatingStyles ? refs.setReference : null} warnText={warnText} warnTextId={warnTextId}>
diff --git a/packages/react/src/components/ComboButton/index.tsx b/packages/react/src/components/ComboButton/index.tsx index 178a5ca3fb09..04c2f8f2b5af 100644 --- a/packages/react/src/components/ComboButton/index.tsx +++ b/packages/react/src/components/ComboButton/index.tsx @@ -22,6 +22,7 @@ import { autoUpdate, } from '@floating-ui/react'; import { hide } from '@floating-ui/dom'; +import { useFeatureFlag } from '../FeatureFlags'; import mergeRefs from '../../tools/mergeRefs'; import { MenuAlignment } from '../MenuButton'; import { TranslateWithId } from '../../types/common'; @@ -97,10 +98,20 @@ const ComboButton = React.forwardRef( }, forwardRef ) { + // feature flag utilized to separate out only the dynamic styles from @floating-ui + // flag is turned on when collision detection (ie. flip, hide) logic is not desired + const enableOnlyFloatingStyles = useFeatureFlag( + 'enable-v12-dynamic-floating-styles' + ); + const id = useId('combobutton'); const prefix = usePrefix(); const containerRef = useRef(null); - const middlewares = [flip({ crossAxis: false }), hide()]; + let middlewares: any[] = []; + + if (!enableOnlyFloatingStyles) { + middlewares = [flip({ crossAxis: false }), hide()]; + } if (menuAlignment === 'bottom' || menuAlignment === 'top') { middlewares.push( diff --git a/packages/react/src/components/Dropdown/Dropdown.tsx b/packages/react/src/components/Dropdown/Dropdown.tsx index f31030ff4406..51de97b91f87 100644 --- a/packages/react/src/components/Dropdown/Dropdown.tsx +++ b/packages/react/src/components/Dropdown/Dropdown.tsx @@ -48,6 +48,7 @@ import { size as floatingSize, } from '@floating-ui/react'; import { hide } from '@floating-ui/dom'; +import { useFeatureFlag } from '../FeatureFlags'; const { ItemMouseMove, MenuMouseLeave } = useSelect.stateChangeTypes as UseSelectInterface['stateChangeTypes'] & { @@ -274,8 +275,12 @@ const Dropdown = React.forwardRef( }: DropdownProps, ref: ForwardedRef ) => { + const enableFloatingStyles = useFeatureFlag( + 'enable-v12-dynamic-floating-styles' + ); + const { refs, floatingStyles, middlewareData } = useFloating( - autoAlign + enableFloatingStyles || autoAlign ? { placement: direction, @@ -294,16 +299,18 @@ const Dropdown = React.forwardRef( }); }, }), - flip(), - hide(), + autoAlign && flip(), + autoAlign && hide(), ], whileElementsMounted: autoUpdate, } - : {} // When autoAlign is turned off, floating-ui will not be used + : {} + // When autoAlign is turned off & the `enable-v12-dynamic-floating-styles` feature flag is not + // enabled, floating-ui will not be used ); useEffect(() => { - if (autoAlign) { + if (enableFloatingStyles || autoAlign) { const updatedFloatingStyles = { ...floatingStyles, visibility: middlewareData.hide?.referenceHidden @@ -503,7 +510,7 @@ const Dropdown = React.forwardRef( const menuProps = useMemo( () => getMenuProps({ - ref: autoAlign ? refs.setFloating : null, + ref: enableFloatingStyles || autoAlign ? refs.setFloating : null, }), [autoAlign, getMenuProps, refs.setFloating] ); @@ -534,7 +541,7 @@ const Dropdown = React.forwardRef( warnText={warnText} light={light} isOpen={isOpen} - ref={autoAlign ? refs.setReference : null} + ref={enableFloatingStyles || autoAlign ? refs.setReference : null} id={id}> {invalid && ( diff --git a/packages/react/src/components/FeatureFlags/overview.mdx b/packages/react/src/components/FeatureFlags/overview.mdx index e924beaa78b9..b4f398cef2bd 100644 --- a/packages/react/src/components/FeatureFlags/overview.mdx +++ b/packages/react/src/components/FeatureFlags/overview.mdx @@ -44,15 +44,16 @@ For more details on this approach, see the [feature flag documentation](https://github.com/carbon-design-system/carbon/blob/main/docs/experimental-code.md) in the Carbon monorepo. -| Flag | Description | Default | Javascript flag | Sass flag | -| -------------------------------------------------- | ------------------------------------------------------------------------ | ------- | --------------- | --------- | -| `enable-experimental-tile-contrast` | Enable the improved styling for tiles that provides better contrast | `false` | | ✅ | -| `enable-experimental-focus-wrap-without-sentinels` | Enable the new focus wrap behavior that doesn't use sentinel nodes | `false` | ✅ | | -| `enable-treeview-controllable` | Enable the new TreeView controllable API | `false` | ✅ | | -| `enable-v12-tile-default-icons` | Enable default icons for Tile components | `false` | ✅ | | -| `enable-v12-overflowmenu` | Enable the use of the v12 OverflowMenu leveraging the Menu subcomponents | `false` | ✅ | | -| `enable-v12-tile-radio-icons` | Enable rendering of default icons in the tile components | `false` | ✅ | ✅ | -| `enable-v12-structured-list-visible-icons` | Enable icon components within StructuredList to always be visible | `false` | | ✅ | +| Flag | Description | Default | Javascript flag | Sass flag | +| -------------------------------------------------- | ------------------------------------------------------------------------------------ | ------- | --------------- | --------- | +| `enable-experimental-tile-contrast` | Enable the improved styling for tiles that provides better contrast | `false` | | ✅ | +| `enable-experimental-focus-wrap-without-sentinels` | Enable the new focus wrap behavior that doesn't use sentinel nodes | `false` | ✅ | | +| `enable-treeview-controllable` | Enable the new TreeView controllable API | `false` | ✅ | | +| `enable-v12-tile-default-icons` | Enable default icons for Tile components | `false` | ✅ | | +| `enable-v12-overflowmenu` | Enable the use of the v12 OverflowMenu leveraging the Menu subcomponents | `false` | ✅ | | +| `enable-v12-tile-radio-icons` | Enable rendering of default icons in the tile components | `false` | ✅ | ✅ | +| `enable-v12-structured-list-visible-icons` | Enable icon components within StructuredList to always be visible | `false` | | ✅ | +| `enable-v12-dynamic-floating-styles` | Enable dynamic setting of floating styles for components like Popover, Tooltip, etc. | `false` | ✅ | ✅ | ## Turning on feature flags in Javascript/react diff --git a/packages/react/src/components/MenuButton/index.tsx b/packages/react/src/components/MenuButton/index.tsx index dfba2ae64bc5..b2300ea2e55d 100644 --- a/packages/react/src/components/MenuButton/index.tsx +++ b/packages/react/src/components/MenuButton/index.tsx @@ -28,6 +28,7 @@ import { size as floatingSize, autoUpdate, } from '@floating-ui/react'; +import { useFeatureFlag } from '../FeatureFlags'; import mergeRefs from '../../tools/mergeRefs'; const validButtonKinds = ['primary', 'tertiary', 'ghost']; @@ -97,10 +98,20 @@ const MenuButton = forwardRef( }, forwardRef ) { + // feature flag utilized to separate out only the dynamic styles from @floating-ui + // flag is turned on when collision detection (ie. flip, hide) logic is not desired + const enableOnlyFloatingStyles = useFeatureFlag( + 'enable-v12-dynamic-floating-styles' + ); + const id = useId('MenuButton'); const prefix = usePrefix(); const triggerRef = useRef(null); - const middlewares = [flip({ crossAxis: false })]; + let middlewares: any[] = []; + + if (!enableOnlyFloatingStyles) { + middlewares = [flip({ crossAxis: false })]; + } if (menuAlignment === 'bottom' || menuAlignment === 'top') { middlewares.push( @@ -113,6 +124,7 @@ const MenuButton = forwardRef( }) ); } + const { refs, floatingStyles, placement, middlewareData } = useFloating({ placement: menuAlignment, diff --git a/packages/react/src/components/MultiSelect/MultiSelect.tsx b/packages/react/src/components/MultiSelect/MultiSelect.tsx index 392f16774cba..664412d98534 100644 --- a/packages/react/src/components/MultiSelect/MultiSelect.tsx +++ b/packages/react/src/components/MultiSelect/MultiSelect.tsx @@ -53,6 +53,7 @@ import { autoUpdate, } from '@floating-ui/react'; import { hide } from '@floating-ui/dom'; +import { useFeatureFlag } from '../FeatureFlags'; const { ItemClick, @@ -362,8 +363,11 @@ const MultiSelect = React.forwardRef( const [topItems, setTopItems] = useState([]); const [itemsCleared, setItemsCleared] = useState(false); + const enableFloatingStyles = + useFeatureFlag('enable-v12-dynamic-floating-styles') || autoAlign; + const { refs, floatingStyles, middlewareData } = useFloating( - autoAlign + enableFloatingStyles ? { placement: direction, @@ -375,7 +379,7 @@ const MultiSelect = React.forwardRef( // Middleware order matters, arrow should be last middleware: [ - flip({ crossAxis: false }), + autoAlign && flip({ crossAxis: false }), floatingSize({ apply({ rects, elements }) { Object.assign(elements.floating.style, { @@ -383,7 +387,7 @@ const MultiSelect = React.forwardRef( }); }, }), - hide(), + autoAlign && hide(), ], whileElementsMounted: autoUpdate, } @@ -391,7 +395,7 @@ const MultiSelect = React.forwardRef( ); useLayoutEffect(() => { - if (autoAlign) { + if (enableFloatingStyles) { const updatedFloatingStyles = { ...floatingStyles, visibility: middlewareData.hide?.referenceHidden @@ -404,7 +408,13 @@ const MultiSelect = React.forwardRef( } }); } - }, [autoAlign, floatingStyles, refs.floating, middlewareData, open]); + }, [ + enableFloatingStyles, + floatingStyles, + refs.floating, + middlewareData, + open, + ]); const { selectedItems: controlledSelectedItems, @@ -551,7 +561,7 @@ const MultiSelect = React.forwardRef( selectedItems && selectedItems.length > 0, [`${prefix}--list-box--up`]: direction === 'top', [`${prefix}--multi-select--readonly`]: readOnly, - [`${prefix}--autoalign`]: autoAlign, + [`${prefix}--autoalign`]: enableFloatingStyles, [`${prefix}--multi-select--selectall`]: selectAll, }); @@ -689,9 +699,9 @@ const MultiSelect = React.forwardRef( const menuProps = useMemo( () => getMenuProps({ - ref: autoAlign ? refs.setFloating : null, + ref: enableFloatingStyles ? refs.setFloating : null, }), - [autoAlign, getMenuProps, refs.setFloating] + [enableFloatingStyles, getMenuProps, refs.setFloating] ); return ( @@ -729,7 +739,7 @@ const MultiSelect = React.forwardRef( )}
+ ref={enableFloatingStyles ? refs.setReference : null}> {selectedItems.length > 0 && ( ( }, forwardRef ) { + const enableFloatingStyles = + useFeatureFlag('enable-v12-dynamic-floating-styles') || autoAlign; + const { refs, floatingStyles, placement, middlewareData } = useFloating( - autoAlign + enableFloatingStyles ? { // Computing the position starts with initial positioning // via `placement`. @@ -108,18 +112,21 @@ const OverflowMenu = React.forwardRef( // initial `placement` computation and eventual return of data for // rendering. Each middleware is executed in order. middleware: [ - flip({ - // An explicit array of placements to try if the initial - // `placement` doesn’t fit on the axes in which overflow - // is checked. - fallbackPlacements: menuAlignment.includes('bottom') - ? ['bottom-start', 'bottom-end', 'top-start', 'top-end'] - : ['top-start', 'top-end', 'bottom-start', 'bottom-end'], - }), + autoAlign && + flip({ + // An explicit array of placements to try if the initial + // `placement` doesn’t fit on the axes in which overflow + // is checked. + fallbackPlacements: menuAlignment.includes('bottom') + ? ['bottom-start', 'bottom-end', 'top-start', 'top-end'] + : ['top-start', 'top-end', 'bottom-start', 'bottom-end'], + }), ], whileElementsMounted: autoUpdate, } - : {} // When autoAlign is turned off, floating-ui will not be used + : {} + // When autoAlign is turned off & the `enable-v12-dynamic-floating-styles` feature flag is not + // enabled, floating-ui will not be used ); const id = useId('overflowmenu'); @@ -136,7 +143,7 @@ const OverflowMenu = React.forwardRef( handleClose, } = useAttachedMenu(triggerRef); useEffect(() => { - if (autoAlign) { + if (enableFloatingStyles) { Object.keys(floatingStyles).forEach((style) => { if (refs.floating.current) { refs.floating.current.style[style] = floatingStyles[style]; @@ -145,7 +152,7 @@ const OverflowMenu = React.forwardRef( } }, [ floatingStyles, - autoAlign, + enableFloatingStyles, refs.floating, open, placement, @@ -161,7 +168,7 @@ const OverflowMenu = React.forwardRef( const containerClasses = classNames( className, `${prefix}--overflow-menu__container`, - { [`${prefix}--autoalign`]: autoAlign } + { [`${prefix}--autoalign`]: enableFloatingStyles } ); const menuClasses = classNames( @@ -204,7 +211,7 @@ const OverflowMenu = React.forwardRef( className={menuClasses} id={id} size={size} - legacyAutoalign={!autoAlign} + legacyAutoalign={!enableFloatingStyles} open={open} onClose={handleClose} x={x} diff --git a/packages/react/src/components/Popover/DynamicStyles.featureflag.mdx b/packages/react/src/components/Popover/DynamicStyles.featureflag.mdx new file mode 100644 index 000000000000..2e7ebd8f7704 --- /dev/null +++ b/packages/react/src/components/Popover/DynamicStyles.featureflag.mdx @@ -0,0 +1,42 @@ +import { Meta } from '@storybook/blocks'; + + + +# Dynamically set floating styles + +Components with `autoAlign` use the +[`floating-ui`](https://github.com/floating-ui/floating-ui) library to handle +the dynamic positioning of floating elements relative to their trigger / anchor +element. This also includes collision detection that repositions the floating +element as necessary to keep it visible within the viewport. In the majority of +cases, the styling from the `floating-ui` 'fixed' positioning strategy prevents +the floating element from being +[clipped](https://floating-ui.com/docs/misc#clipping) by an ancestor element. + +The `enable-v12-dynamic-floating-styles` flag enables this dynamic styling +without the collision detection. + +**Note**: The flag has no effect when used with `autoAlign` because `autoAlign` +includes both the dynamic positioning styles and collision detection. + +## Enable dynamic setting of floating styles + +```js + + + + + The content that is revealed by interacting with the Toggle button + + + +``` diff --git a/packages/react/src/components/Popover/DynamicStyles.featureflag.stories.js b/packages/react/src/components/Popover/DynamicStyles.featureflag.stories.js new file mode 100644 index 000000000000..29f5de862e7d --- /dev/null +++ b/packages/react/src/components/Popover/DynamicStyles.featureflag.stories.js @@ -0,0 +1,409 @@ +/** + * Copyright IBM Corp. 2016, 2023 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, { useState } from 'react'; +import { Information, Checkbox as CheckboxIcon } from '@carbon/icons-react'; +import { ComboBox } from '../ComboBox'; +import { ComboButton } from '../ComboButton'; +import { Dropdown } from '../Dropdown'; +import { Link } from '../Link'; +import { Button } from '../Button'; +import { + ToggletipLabel, + Toggletip, + ToggletipButton, + ToggletipContent, + ToggletipActions, +} from '../Toggletip'; +import { Tooltip } from '../Tooltip'; +import { MenuItem, MenuItemDivider } from '../Menu'; +import { MenuButton } from '../MenuButton'; +import { MultiSelect } from '../MultiSelect'; +import { Popover, PopoverContent } from '../Popover'; +import { OverflowMenu } from '../OverflowMenu'; +import { action } from '@storybook/addon-actions'; +import mdx from './DynamicStyles.featureflag.mdx'; +import { WithFeatureFlags } from '../../../.storybook/templates/WithFeatureFlags'; + +import './story.scss'; +import '../Tooltip/story.scss'; + +// eslint-disable-next-line storybook/csf-component +export default { + title: 'Experimental/Feature Flags/Dynamic floating styles', + component: Dropdown, + parameters: { + docs: { + page: mdx, + }, + }, + decorators: [ + (Story) => ( + + + + ), + ], +}; + +const comboBoxItems = [ + { + id: 'option-0', + text: 'An example option that is really long to show what should be done to handle long text', + }, + { + id: 'option-1', + text: 'Option 1', + }, + { + id: 'option-2', + text: 'Option 2', + }, + { + id: 'option-3', + text: 'Option 3 - a disabled item', + disabled: true, + }, + { + id: 'option-4', + text: 'Option 4', + }, + { + id: 'option-5', + text: 'Option 5', + }, +]; + +export const _ComboBox = (args) => ( + {}} + id="carbon-combobox" + items={comboBoxItems} + itemToString={(item) => (item ? item.text : '')} + titleText="ComboBox title" + helperText="Combobox helper text" + {...args} + /> +); + +_ComboBox.args = { + direction: 'bottom', +}; + +_ComboBox.argTypes = { + direction: { + options: ['top', 'bottom'], + control: { + type: 'radio', + }, + }, +}; + +export const _ComboButton = (args) => ( + + + + + +); + +_ComboButton.args = { + menuAlignment: 'bottom', +}; + +_ComboButton.argTypes = { + menuAlignment: { + options: ['top', 'bottom'], + control: { + type: 'radio', + }, + }, +}; + +const items = [ + { + text: 'Lorem, ipsum dolor sit amet consectetur adipisicing elit.', + }, + { + text: 'Option 1', + }, + { + text: 'Option 2', + }, + { + text: 'Option 3 - a disabled item', + disabled: true, + }, + { + text: 'Option 4', + }, + { + text: 'Option 5', + }, + { + text: 'Option 6', + }, + { + text: 'Option 7', + }, + { + text: 'Option 8', + }, +]; + +export const _Dropdown = (args) => ( + (item ? item.text : '')} + {...args} + /> +); + +_Dropdown.args = { + direction: 'bottom', +}; + +_Dropdown.argTypes = { + direction: { + options: ['top', 'bottom'], + control: { + type: 'radio', + }, + }, +}; + +export const _Popover = (args) => { + const [open, setOpen] = useState(true); + + return ( +
+ +
+ { + setOpen(!open); + }} + /> +
+ +
+

This popover uses autoAlign

+

+ Scroll the container up, down, left or right to observe how the + popover will automatically change its position in attempt to stay + within the viewport. This works on initial render in addition to + on scroll. +

+
+
+
+
+ ); +}; + +_Popover.args = { + align: 'bottom', +}; + +_Popover.argTypes = { + align: { + options: [ + 'top', + 'top-start', + 'top-end', + + 'bottom', + 'bottom-start', + 'bottom-end', + + 'left', + 'left-end', + 'left-start', + + 'right', + 'right-end', + 'right-start', + ], + control: { + type: 'select', + }, + }, +}; + +export const _MenuButton = (args) => ( + + + + + +); + +_MenuButton.args = { + menuAlignment: 'bottom', +}; + +_MenuButton.argTypes = { + menuAlignment: { + options: ['top', 'bottom'], + control: { + type: 'radio', + }, + }, +}; + +export const _MultiSelect = (args) => ( + (item ? item.text : '')} + selectionFeedback="top-after-reopen" + /> +); + +_MultiSelect.args = { + direction: 'bottom', +}; + +_MultiSelect.argTypes = { + direction: { + options: ['top', 'bottom'], + control: { + type: 'radio', + }, + }, +}; + +export const _Toggletip = (args) => { + return ( +
+ Toggletip label + + + + + +

+ Scroll the container up, down, left or right to observe how the + Toggletip will automatically change its position in attempt to stay + within the viewport. This works on initial render in addition to on + scroll. +

+ + Link action + + +
+
+
+ ); +}; + +_Toggletip.args = { + align: 'bottom', +}; + +_Toggletip.argTypes = { + align: { + options: [ + 'top', + 'top-start', + 'top-end', + + 'bottom', + 'bottom-start', + 'bottom-end', + + 'left', + 'left-end', + 'left-start', + + 'right', + 'right-end', + 'right-start', + ], + control: { + type: 'select', + }, + }, +}; + +export const _Tooltip = (args) => { + const tooltipLabel = + 'Scroll the container up, down, left or right to observe how the tooltip will automatically change its position in attempt to stay within the viewport. This works on initial render in addition to on scroll.'; + return ( +
+ + + +
+ ); +}; + +_Tooltip.args = { + align: 'bottom', +}; + +_Tooltip.argTypes = { + align: { + options: [ + 'top', + 'top-start', + 'top-end', + + 'bottom', + 'bottom-start', + 'bottom-end', + + 'left', + 'left-end', + 'left-start', + + 'right', + 'right-end', + 'right-start', + ], + control: { + type: 'select', + }, + }, +}; + +export const _OverflowMenu = () => { + return ( +
+ + + + + + + + +
+ ); +}; diff --git a/packages/react/src/components/Popover/index.tsx b/packages/react/src/components/Popover/index.tsx index a57381df00f0..0f96b0070a2f 100644 --- a/packages/react/src/components/Popover/index.tsx +++ b/packages/react/src/components/Popover/index.tsx @@ -30,6 +30,7 @@ import { offset, } from '@floating-ui/react'; import { hide } from '@floating-ui/dom'; +import { useFeatureFlag } from '../FeatureFlags'; interface PopoverContext { setFloating: React.Ref; @@ -179,6 +180,8 @@ export const Popover: PopoverComponent = React.forwardRef( const floating = useRef(null); const caretRef = useRef(null); const popover = useRef(null); + const enableFloatingStyles = + useFeatureFlag('enable-v12-dynamic-floating-styles') || autoAlign; let align = mapPopoverAlignProp(initialAlign); @@ -242,7 +245,7 @@ export const Popover: PopoverComponent = React.forwardRef( }); const { refs, floatingStyles, placement, middlewareData } = useFloating( - autoAlign + enableFloatingStyles ? { placement: align, @@ -255,15 +258,17 @@ export const Popover: PopoverComponent = React.forwardRef( // Middleware order matters, arrow should be last middleware: [ offset(!isTabTip ? popoverDimensions?.current?.offset : 0), - flip({ fallbackAxisSideDirection: 'start' }), + autoAlign && flip({ fallbackAxisSideDirection: 'start' }), arrow({ element: caretRef, }), - hide(), + autoAlign && hide(), ], whileElementsMounted: autoUpdate, } - : {} // When autoAlign is turned off, floating-ui will not be used + : {} + // When autoAlign is turned off & the `enable-v12-dynamic-floating-styles` feature flag is not + // enabled, floating-ui will not be used ); const value = useMemo(() => { @@ -287,7 +292,7 @@ export const Popover: PopoverComponent = React.forwardRef( } useEffect(() => { - if (autoAlign) { + if (enableFloatingStyles) { const updatedFloatingStyles = { ...floatingStyles, visibility: middlewareData.hide?.referenceHidden @@ -331,7 +336,7 @@ export const Popover: PopoverComponent = React.forwardRef( }, [ floatingStyles, refs.floating, - autoAlign, + enableFloatingStyles, middlewareData, placement, caret, @@ -347,7 +352,8 @@ export const Popover: PopoverComponent = React.forwardRef( [`${prefix}--popover--drop-shadow`]: dropShadow, [`${prefix}--popover--high-contrast`]: highContrast, [`${prefix}--popover--open`]: open, - [`${prefix}--popover--auto-align ${prefix}--autoalign`]: autoAlign, + [`${prefix}--popover--auto-align ${prefix}--autoalign`]: + enableFloatingStyles, [`${prefix}--popover--${currentAlignment}`]: true, [`${prefix}--popover--tab-tip`]: isTabTip, }, @@ -367,9 +373,11 @@ export const Popover: PopoverComponent = React.forwardRef( */ const isTriggerElement = item?.type === 'button'; const isTriggerComponent = - autoAlign && displayName && ['ToggletipButton'].includes(displayName); + enableFloatingStyles && + displayName && + ['ToggletipButton'].includes(displayName); const isAllowedTriggerComponent = - autoAlign && + enableFloatingStyles && !['ToggletipContent', 'PopoverContent'].includes(displayName); if ( @@ -402,9 +410,9 @@ export const Popover: PopoverComponent = React.forwardRef( // In either of these caes we want to set this as the reference node for floating-ui autoAlign // positioning. if ( - (autoAlign && + (enableFloatingStyles && (item?.type as any)?.displayName !== 'PopoverContent') || - (autoAlign && + (enableFloatingStyles && (item?.type as any)?.displayName === 'ToggletipButton') ) { // Set the reference element for floating-ui @@ -429,7 +437,7 @@ export const Popover: PopoverComponent = React.forwardRef( return ( - {autoAlign || isTabTip ? mappedChildren : children} + {enableFloatingStyles || isTabTip ? mappedChildren : children} ); @@ -445,7 +453,6 @@ if (__DEV__) { Popover.propTypes = { /** * Specify how the popover should align with the trigger element - */ align: deprecateValuesWithin( PropTypes.oneOf([ @@ -558,12 +565,14 @@ function PopoverContentRenderFunction( const prefix = usePrefix(); const { setFloating, caretRef, autoAlign } = React.useContext(PopoverContext); const ref = useMergedRefs([setFloating, forwardRef]); + const enableFloatingStyles = + useFeatureFlag('enable-v12-dynamic-floating-styles') || autoAlign; return ( {children} - {autoAlign && ( + {enableFloatingStyles && ( )} - {!autoAlign && ( + {!enableFloatingStyles && (