From 6dfa36605d20ddb4de5a5b3251e20118690a09a9 Mon Sep 17 00:00:00 2001 From: Kenta Iwasaki <63115601+lithdew@users.noreply.github.com> Date: Wed, 31 Jul 2024 07:47:37 +0800 Subject: [PATCH] fix(RAC): support hover events for Tabs (#6742) support hover events for Tab --- packages/react-aria-components/src/Tabs.tsx | 9 ++++-- .../react-aria-components/test/Tabs.test.js | 30 ++++++++++++++++++- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/packages/react-aria-components/src/Tabs.tsx b/packages/react-aria-components/src/Tabs.tsx index 75d13d9dcf0..f2ec9818837 100644 --- a/packages/react-aria-components/src/Tabs.tsx +++ b/packages/react-aria-components/src/Tabs.tsx @@ -10,7 +10,7 @@ * governing permissions and limitations under the License. */ -import {AriaLabelingProps, forwardRefType, Key, LinkDOMProps, RefObject} from '@react-types/shared'; +import {AriaLabelingProps, forwardRefType, HoverEvents, Key, LinkDOMProps, RefObject} from '@react-types/shared'; import {AriaTabListProps, AriaTabPanelProps, mergeProps, Orientation, useFocusRing, useHover, useTab, useTabList, useTabPanel} from 'react-aria'; import {Collection, CollectionBuilder, createHideableComponent, createLeafComponent} from '@react-aria/collections'; import {CollectionProps, CollectionRendererContext, usePersistedKeys} from './Collection'; @@ -43,7 +43,7 @@ export interface TabListRenderProps { state: TabListState } -export interface TabProps extends RenderProps, AriaLabelingProps, LinkDOMProps { +export interface TabProps extends RenderProps, AriaLabelingProps, LinkDOMProps, HoverEvents { /** The unique id of the tab. */ id?: Key, /** Whether the tab is disabled. */ @@ -252,7 +252,10 @@ export const Tab = /*#__PURE__*/ createLeafComponent('item', (props: TabProps, f let {tabProps, isSelected, isDisabled, isPressed} = useTab({key: item.key, ...props}, state, ref); let {focusProps, isFocused, isFocusVisible} = useFocusRing(); let {hoverProps, isHovered} = useHover({ - isDisabled + isDisabled, + onHoverStart: props.onHoverStart, + onHoverEnd: props.onHoverEnd, + onHoverChange: props.onHoverChange }); let renderProps = useRenderProps({ diff --git a/packages/react-aria-components/test/Tabs.test.js b/packages/react-aria-components/test/Tabs.test.js index 5b899858ced..5b471156605 100644 --- a/packages/react-aria-components/test/Tabs.test.js +++ b/packages/react-aria-components/test/Tabs.test.js @@ -116,7 +116,10 @@ describe('Tabs', () => { }); it('should support hover', async () => { - let {getAllByRole} = renderTabs({}, {}, {className: ({isHovered}) => isHovered ? 'hover' : ''}); + let onHoverStart = jest.fn(); + let onHoverChange = jest.fn(); + let onHoverEnd = jest.fn(); + let {getAllByRole} = renderTabs({}, {}, {className: ({isHovered}) => isHovered ? 'hover' : '', onHoverStart, onHoverChange, onHoverEnd}); let tab = getAllByRole('tab')[0]; expect(tab).not.toHaveAttribute('data-hovered'); @@ -125,10 +128,35 @@ describe('Tabs', () => { await user.hover(tab); expect(tab).toHaveAttribute('data-hovered', 'true'); expect(tab).toHaveClass('hover'); + expect(onHoverStart).toHaveBeenCalledTimes(1); + expect(onHoverChange).toHaveBeenCalledTimes(1); await user.unhover(tab); expect(tab).not.toHaveAttribute('data-hovered'); expect(tab).not.toHaveClass('hover'); + expect(onHoverEnd).toHaveBeenCalledTimes(1); + expect(onHoverChange).toHaveBeenCalledTimes(2); + }); + + it('should not show hover state when item is not interactive', async () => { + let onHoverStart = jest.fn(); + let onHoverChange = jest.fn(); + let onHoverEnd = jest.fn(); + let {getAllByRole} = renderTabs({disabledKeys: ['a', 'b', 'c']}, {}, {className: ({isHovered}) => isHovered ? 'hover' : '', onHoverStart, onHoverChange, onHoverEnd}); + let tab = getAllByRole('tab')[0]; + + expect(tab).not.toHaveAttribute('data-hovered'); + expect(tab).not.toHaveClass('hover'); + expect(onHoverStart).not.toHaveBeenCalled(); + expect(onHoverChange).not.toHaveBeenCalled(); + expect(onHoverEnd).not.toHaveBeenCalled(); + + await user.hover(tab); + expect(tab).not.toHaveAttribute('data-hovered'); + expect(tab).not.toHaveClass('hover'); + expect(onHoverStart).not.toHaveBeenCalled(); + expect(onHoverChange).not.toHaveBeenCalled(); + expect(onHoverEnd).not.toHaveBeenCalled(); }); it('should support focus ring', async () => {