diff --git a/src/components/ActionTooltip/ActionTooltip.tsx b/src/components/ActionTooltip/ActionTooltip.tsx index 7d372d6ccc..5d16e885be 100644 --- a/src/components/ActionTooltip/ActionTooltip.tsx +++ b/src/components/ActionTooltip/ActionTooltip.tsx @@ -63,7 +63,6 @@ export function ActionTooltip(props: ActionTooltipProps) { anchorElement={anchorElement} disableEscapeKeyDown disableOutsideClick - disableLayer qa={qa} >
diff --git a/src/components/ActionTooltip/__tests__/ActionTooltip.test.tsx b/src/components/ActionTooltip/__tests__/ActionTooltip.test.tsx index 338026f763..f95a463449 100644 --- a/src/components/ActionTooltip/__tests__/ActionTooltip.test.tsx +++ b/src/components/ActionTooltip/__tests__/ActionTooltip.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import userEvent from '@testing-library/user-event'; -import {createEvent, fireEvent, render, screen} from '../../../../test-utils/utils'; +import {createEvent, fireEvent, render, screen, waitFor} from '../../../../test-utils/utils'; import {ActionTooltip} from '../ActionTooltip'; export function fireAnimationEndEvent(el: Node | Window, animationName = 'animation') { @@ -46,7 +46,9 @@ test('should show tooltip on hover and hide on un hover', async () => { fireAnimationEndEvent(tooltip); - expect(tooltip).not.toBeInTheDocument(); + await waitFor(() => { + expect(tooltip).not.toBeInTheDocument(); + }); }); test('should show tooltip on focus and hide on blur', async () => { @@ -71,7 +73,9 @@ test('should show tooltip on focus and hide on blur', async () => { fireAnimationEndEvent(tooltip); expect(button).not.toHaveFocus(); - expect(tooltip).not.toBeInTheDocument(); + await waitFor(() => { + expect(tooltip).not.toBeInTheDocument(); + }); }); test('should hide on press Escape', async () => { @@ -96,7 +100,9 @@ test('should hide on press Escape', async () => { fireAnimationEndEvent(tooltip); expect(button).toHaveFocus(); - expect(tooltip).not.toBeInTheDocument(); + await waitFor(() => { + expect(tooltip).not.toBeInTheDocument(); + }); }); test('should show on focus and hide on un hover', async () => { @@ -124,5 +130,7 @@ test('should show on focus and hide on un hover', async () => { fireAnimationEndEvent(tooltip); expect(button).toHaveFocus(); - expect(tooltip).not.toBeInTheDocument(); + await waitFor(() => { + expect(tooltip).not.toBeInTheDocument(); + }); }); diff --git a/src/components/Dialog/DialogFooter/DialogFooter.tsx b/src/components/Dialog/DialogFooter/DialogFooter.tsx index c87b52428f..c416d684fa 100644 --- a/src/components/Dialog/DialogFooter/DialogFooter.tsx +++ b/src/components/Dialog/DialogFooter/DialogFooter.tsx @@ -135,7 +135,6 @@ export class DialogFooter extends React.Component { open={showError} anchorRef={this.errorTooltipRef} placement={['bottom', 'top']} - disableLayer disablePortal hasArrow > diff --git a/src/components/DropdownMenu/DropdownMenuPopup.tsx b/src/components/DropdownMenu/DropdownMenuPopup.tsx index df1e3a639a..dfc9cb88de 100644 --- a/src/components/DropdownMenu/DropdownMenuPopup.tsx +++ b/src/components/DropdownMenu/DropdownMenuPopup.tsx @@ -57,7 +57,9 @@ export const DropdownMenuPopup = ({ const handleMouseEnter = React.useCallback( (event: React.MouseEvent) => { setActiveMenuPath(path); - popupProps?.onMouseEnter?.(event); + (popupProps?.floatingProps?.onMouseEnter as React.MouseEventHandler | undefined)?.( + event, + ); }, [path, popupProps, setActiveMenuPath], ); @@ -65,7 +67,9 @@ export const DropdownMenuPopup = ({ const handleMouseLeave = React.useCallback( (event: React.MouseEvent) => { activateParent(); - popupProps?.onMouseLeave?.(event); + (popupProps?.floatingProps?.onMouseLeave as React.MouseEventHandler | undefined)?.( + event, + ); }, [activateParent, popupProps], ); @@ -144,8 +148,11 @@ export const DropdownMenuPopup = ({ onClose={onClose} placement="bottom-start" {...popupProps} - onMouseEnter={handleMouseEnter} - onMouseLeave={handleMouseLeave} + floatingProps={{ + ...popupProps?.floatingProps, + onMouseEnter: handleMouseEnter, + onMouseLeave: handleMouseLeave, + }} > {children || ( diff --git a/src/components/Popover/Popover.scss b/src/components/Popover/Popover.scss index 7b0a9e1550..415f763b68 100644 --- a/src/components/Popover/Popover.scss +++ b/src/components/Popover/Popover.scss @@ -24,13 +24,11 @@ $block: '.#{variables.$ns}popover'; --_--close-offset: 8px; --_--close-size: 24px; - &-popup-content { - box-sizing: border-box; - min-height: 40px; - max-width: var(--g-popover-max-width, 300px); - padding: var(--g-popover-padding, var(--_--padding)); - cursor: default; - } + box-sizing: border-box; + min-height: 40px; + max-width: var(--g-popover-max-width, 300px); + padding: var(--g-popover-padding, var(--_--padding)); + cursor: default; &-title { @include mixins.text-subheader-3(); diff --git a/src/components/Popover/Popover.tsx b/src/components/Popover/Popover.tsx index d3d46950e8..9596538095 100644 --- a/src/components/Popover/Popover.tsx +++ b/src/components/Popover/Popover.tsx @@ -37,7 +37,6 @@ export const Popover = React.forwardRef diff --git a/src/components/Popover/__stories__/Popover.stories.tsx b/src/components/Popover/__stories__/Popover.stories.tsx index 42fb8cee50..a71efc178e 100644 --- a/src/components/Popover/__stories__/Popover.stories.tsx +++ b/src/components/Popover/__stories__/Popover.stories.tsx @@ -75,7 +75,6 @@ const meta: Meta = { tooltipCancelButton: {control: 'object'}, tooltipOffset: {control: 'object'}, tooltipClassName: {control: 'text'}, - tooltipContentClassName: {control: 'text'}, className: {control: 'text'}, onClick: {action: 'onClick'}, onOpenChange: {action: 'onOpenChange'}, diff --git a/src/components/Popover/__tests__/Popover.test.tsx b/src/components/Popover/__tests__/Popover.test.tsx index 7dd3423bf8..230300274c 100644 --- a/src/components/Popover/__tests__/Popover.test.tsx +++ b/src/components/Popover/__tests__/Popover.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {setupTimersMock} from '../../../../test-utils/setupTimersMock'; -import {act, fireEvent, render, screen} from '../../../../test-utils/utils'; +import {act, fireEvent, render, screen, waitFor} from '../../../../test-utils/utils'; import {Popover} from '../Popover'; import {PopoverBehavior, delayByBehavior} from '../config'; import type {PopoverProps} from '../types'; @@ -32,11 +32,13 @@ const checkIfPopoverOpened = () => { expect(popover).toHaveClass('g-popup_open'); }; -const checkIfPopoverClosed = () => { +const checkIfPopoverClosed = async () => { const popover = screen.queryByTestId('popover-tooltip'); if (popover) { - expect(popover).not.toHaveClass('g-popup_open'); + await waitFor(() => { + expect(popover).not.toHaveClass('g-popup_open'); + }); } else { expect(true).toBe(true); } diff --git a/src/components/Popover/types.ts b/src/components/Popover/types.ts index cc936a6b05..db2dcfdea6 100644 --- a/src/components/Popover/types.ts +++ b/src/components/Popover/types.ts @@ -44,8 +44,6 @@ export interface PopoverExternalProps { tooltipOffset?: PopupOffset; /** Tooltip's css class */ tooltipClassName?: string; - /** Tooltip's content css class */ - tooltipContentClassName?: string; /** css class for the control */ className?: string; /** @@ -124,7 +122,7 @@ export type PopoverDefaultProps = { export type PopoverProps = Pick< PopupProps, - 'anchorElement' | 'anchorRef' | 'strategy' | 'placement' | 'middlewares' + 'anchorElement' | 'anchorRef' | 'strategy' | 'placement' > & PopoverExternalProps & PopoverBehaviorProps & diff --git a/src/components/Popup/Popup.scss b/src/components/Popup/Popup.scss index ab1eb21c01..377b767d00 100644 --- a/src/components/Popup/Popup.scss +++ b/src/components/Popup/Popup.scss @@ -26,7 +26,7 @@ $transition-distance: 10px; transition-property: opacity, transform; transition-timing-function: ease-out; - &_mounted { + &_open { visibility: visible; } diff --git a/src/components/Popup/Popup.tsx b/src/components/Popup/Popup.tsx index 335f3a05b4..9a24e012c0 100644 --- a/src/components/Popup/Popup.tsx +++ b/src/components/Popup/Popup.tsx @@ -119,8 +119,12 @@ export interface PopupProps extends DOMProps, AriaLabelingProps, QAProps { id?: string; /** CSS property `z-index` */ zIndex?: number; + /** Callback called when `Popup` is opened and "in" transition is started */ + onTransitionIn?: () => void; /** Callback called when `Popup` is opened and "in" transition is completed */ onTransitionInComplete?: () => void; + /** Callback called when `Popup` is closed and "out" transition is started */ + onTransitionOut?: () => void; /** Callback called when `Popup` is closed and "out" transition is completed */ onTransitionOutComplete?: () => void; } @@ -160,6 +164,8 @@ export function Popup({ id, role: roleProp, zIndex = 1000, + onTransitionIn, + onTransitionOut, onTransitionInComplete, onTransitionOutComplete, ...restProps @@ -231,8 +237,8 @@ export function Popup({ }); const role = useRole(context, { - enabled: Boolean(roleProp), - role: roleProp, + enabled: Boolean(roleProp || modalFocus), + role: roleProp ?? (modalFocus ? 'dialog' : undefined), }); const dismiss = useDismiss(context, { enabled: !disableOutsideClick || !disableEscapeKeyDown, @@ -274,19 +280,25 @@ export function Popup({ [status, onTransitionInComplete], ); - // Cannot use transitionend event for "out" transition due to unmounting from the DOM + // Cannot use transitionend event for these callbacks due to unmounting from the DOM React.useEffect(() => { + if (status === 'initial' && previousStatus === 'unmounted') { + onTransitionIn?.(); + } + if (status === 'close' && previousStatus === 'open') { + onTransitionOut?.(); + } if (status === 'unmounted' && previousStatus === 'close') { onTransitionOutComplete?.(); } - }, [status, previousStatus, onTransitionOutComplete]); + }, [status, previousStatus, onTransitionIn, onTransitionOut, onTransitionOutComplete]); return isMounted || keepMounted ? (
{ expect(popup).toHaveClass(arbitratyClassName); }); - test('should pass arbitraty className to content', () => { - const arbitratyClassName = 'arbitratyClassName'; - render(); - const popup = screen.getByTestId(qaId); - /* eslint-disable-next-line testing-library/no-node-access */ - expect(popup.firstChild).toHaveClass(arbitratyClassName); - }); - test('should open on click', async () => { const btnText = 'Click me'; function Test() { @@ -57,27 +49,27 @@ describe('Popup', () => { expect(popup).not.toHaveAttribute('role'); }); - test('should set aria-modal to true and role to dialog if focusTrap is true', async () => { - render(); + test('should set aria-modal to true and role to dialog if modalFocus is true', async () => { + render(); const popup = screen.getByRole('dialog'); expect(popup).toHaveAttribute('aria-modal', 'true'); }); - test('should use role from props if focusTrap is true', async () => { - render(); + test('should use role from props if modalFocus is true', async () => { + render(); const popup = screen.getByRole('alertdialog'); expect(popup).toHaveAttribute('aria-modal', 'true'); }); - test('should use aria-modal from props if focusTrap is true', async () => { - render(); + test('should not set aria-modal from props if modalFocus is true', async () => { + render(); const popup = screen.getByTestId(qaId); expect(popup).not.toHaveAttribute('aria-modal'); expect(popup).not.toHaveAttribute('role'); }); test('should remove aria-modal if popup is closed', async () => { - render(); + render(); const popup = screen.getByRole('dialog'); expect(popup).not.toHaveAttribute('aria-modal'); }); diff --git a/src/components/Select/Select.tsx b/src/components/Select/Select.tsx index 22dba73d51..bb627e1d77 100644 --- a/src/components/Select/Select.tsx +++ b/src/components/Select/Select.tsx @@ -221,14 +221,6 @@ export const Select = React.forwardRef(function disabled: filterable, }); - React.useEffect(() => { - if (open) { - if (filterable) { - filterRef.current?.focus(); - } - } - }, [open, filterable]); - const mods: CnMods = { ...(width === 'max' && {width}), }; @@ -355,6 +347,13 @@ export const Select = React.forwardRef(function virtualized={virtualized} mobile={mobile} placement={popupPlacement} + onAfterOpen={ + filterable + ? () => { + filterRef.current?.focus(); + } + : undefined + } onAfterClose={ filterable ? () => { diff --git a/src/components/Select/__stories__/Select.stories.tsx b/src/components/Select/__stories__/Select.stories.tsx index 2a2588623e..82417fe125 100644 --- a/src/components/Select/__stories__/Select.stories.tsx +++ b/src/components/Select/__stories__/Select.stories.tsx @@ -4,6 +4,7 @@ import type {Meta, StoryObj} from '@storybook/react'; import {Select} from '..'; import {Button} from '../../Button'; +import {Flex} from '../../layout'; import {SelectPopupWidthShowcase} from './SelectPopupWidthShowcase'; import {SelectShowcase} from './SelectShowcase'; @@ -34,12 +35,15 @@ type Story = StoryObj; export const Default = { render: (args) => ( - + + + + ), } satisfies Story; diff --git a/src/components/Select/components/SelectPopup/SelectPopup.tsx b/src/components/Select/components/SelectPopup/SelectPopup.tsx index f3ef6c85d6..57fbc89635 100644 --- a/src/components/Select/components/SelectPopup/SelectPopup.tsx +++ b/src/components/Select/components/SelectPopup/SelectPopup.tsx @@ -21,6 +21,7 @@ export const SelectPopup = React.forwardRef( ( { handleClose, + onAfterOpen, onAfterClose, width, open, @@ -46,18 +47,20 @@ export const SelectPopup = React.forwardRef( ) : ( } placement={placement} open={open} onClose={handleClose} disablePortal={disablePortal} - restoreFocus - restoreFocusRef={controlRef} - middlewares={getMiddlewares({width, disablePortal, virtualized})} + autoFocus + initialFocus={-1} + returnFocus={controlRef} + floatingMiddlewares={getMiddlewares({width, disablePortal, virtualized})} id={id} - onTransitionExited={onAfterClose} + onTransitionIn={onAfterOpen} + onTransitionOutComplete={onAfterClose} > {children} diff --git a/src/components/Select/components/SelectPopup/types.ts b/src/components/Select/components/SelectPopup/types.ts index 597d754261..b88d4d1379 100644 --- a/src/components/Select/components/SelectPopup/types.ts +++ b/src/components/Select/components/SelectPopup/types.ts @@ -15,5 +15,6 @@ export type SelectPopupProps = { disablePortal?: boolean; virtualized?: boolean; id?: string; + onAfterOpen?: () => void; onAfterClose?: () => void; }; diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx index 695079319c..6f1cdb3b57 100644 --- a/src/components/Tooltip/Tooltip.tsx +++ b/src/components/Tooltip/Tooltip.tsx @@ -63,7 +63,6 @@ export const Tooltip = (props: TooltipProps) => { disablePortal={disablePortal} disableEscapeKeyDown disableOutsideClick - disableLayer qa={qa} >
diff --git a/src/components/Tooltip/__tests__/Tooltip.test.tsx b/src/components/Tooltip/__tests__/Tooltip.test.tsx index a609d74dd4..bae4bf6557 100644 --- a/src/components/Tooltip/__tests__/Tooltip.test.tsx +++ b/src/components/Tooltip/__tests__/Tooltip.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import userEvent from '@testing-library/user-event'; -import {createEvent, fireEvent, render, screen} from '../../../../test-utils/utils'; +import {createEvent, fireEvent, render, screen, waitFor} from '../../../../test-utils/utils'; import {Tooltip} from '../Tooltip'; export function fireAnimationEndEvent(el: Node | Window, animationName = 'animation') { @@ -44,9 +44,9 @@ test('should show tooltip on hover and hide on un hover', async () => { await user.unhover(button); - fireAnimationEndEvent(tooltip); - - expect(tooltip).not.toBeInTheDocument(); + await waitFor(() => { + expect(tooltip).not.toBeInTheDocument(); + }); }); test('should not show tooltip on focus', async () => { @@ -89,5 +89,8 @@ test('should hide on press Escape', async () => { fireAnimationEndEvent(tooltip); expect(button).toHaveFocus(); - expect(tooltip).not.toBeInTheDocument(); + + await waitFor(() => { + expect(tooltip).not.toBeInTheDocument(); + }); }); diff --git a/src/components/useList/__stories__/components/PopupWithTogglerList.tsx b/src/components/useList/__stories__/components/PopupWithTogglerList.tsx index 8f2f6f17e8..65ce4f22d2 100644 --- a/src/components/useList/__stories__/components/PopupWithTogglerList.tsx +++ b/src/components/useList/__stories__/components/PopupWithTogglerList.tsx @@ -72,8 +72,7 @@ export const PopupWithTogglerList = ({size, itemsCount}: PopupWithTogglerListPro open={open} onClose={() => setOpen(false)} disablePortal - restoreFocus - restoreFocusRef={controlRef} + returnFocus={controlRef} >