Skip to content

Commit f1cd495

Browse files
ryo-manbaLFDanLu
andauthored
Add support for hover events in ListBox Option component (#5777)
* Add support for hover events in ListBox Option component * add newline * fix: modified to original logic * fix: add action to listbox story --------- Co-authored-by: Daniel Lu <dl1644@gmail.com>
1 parent c673f9f commit f1cd495

File tree

3 files changed

+25
-4
lines changed

3 files changed

+25
-4
lines changed

packages/react-aria-components/src/ListBox.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {DragAndDropContext, DragAndDropHooks, DropIndicator, DropIndicatorContex
1717
import {DraggableCollectionState, DroppableCollectionState, ListState, Node, Orientation, SelectionBehavior, useListState} from 'react-stately';
1818
import {filterDOMProps, mergeRefs, useObjectRef} from '@react-aria/utils';
1919
import {Header} from './Header';
20-
import {Key, LinkDOMProps} from '@react-types/shared';
20+
import {HoverEvents, Key, LinkDOMProps} from '@react-types/shared';
2121
import React, {createContext, ForwardedRef, forwardRef, JSX, ReactNode, RefObject, useContext, useEffect, useMemo, useRef} from 'react';
2222
import {Separator, SeparatorContext} from './Separator';
2323
import {TextContext} from './Text';
@@ -332,7 +332,7 @@ function SectionHeader({item, headingProps, headingRef}) {
332332

333333
export interface ListBoxItemRenderProps extends ItemRenderProps {}
334334

335-
export interface ListBoxItemProps<T = object> extends RenderProps<ListBoxItemRenderProps>, LinkDOMProps {
335+
export interface ListBoxItemProps<T = object> extends RenderProps<ListBoxItemRenderProps>, LinkDOMProps, HoverEvents {
336336
/** The unique id of the item. */
337337
id?: Key,
338338
/** The object value that this item represents. When using dynamic collections, this is set automatically. */
@@ -368,7 +368,10 @@ function Option<T>({item}: OptionProps<T>) {
368368
);
369369

370370
let {hoverProps, isHovered} = useHover({
371-
isDisabled: !states.allowsSelection && !states.hasAction
371+
isDisabled: !states.allowsSelection && !states.hasAction,
372+
onHoverStart: item.props.onHoverStart,
373+
onHoverChange: item.props.onHoverChange,
374+
onHoverEnd: item.props.onHoverEnd
372375
});
373376

374377
let draggableItem: DraggableItemResult | null = null;

packages/react-aria-components/stories/ListBox.stories.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13+
import {action} from '@storybook/addon-actions';
1314
import {Header, ListBox, ListBoxItem, ListBoxProps, Section, Separator, Text, useDragAndDrop} from 'react-aria-components';
1415
import {MyListBoxItem} from './utils';
1516
import React from 'react';
@@ -176,3 +177,13 @@ ListBoxDnd.story = {
176177
}
177178
}
178179
};
180+
181+
export const ListBoxHover = () => (
182+
<ListBox className={styles.menu} aria-label="test listbox" onAction={action('onAction')} >
183+
<MyListBoxItem onHoverStart={action('onHoverStart')} onHoverChange={action('onHoverChange')} onHoverEnd={action('onHoverEnd')}>Hover</MyListBoxItem>
184+
<MyListBoxItem>Bar</MyListBoxItem>
185+
<MyListBoxItem>Baz</MyListBoxItem>
186+
<MyListBoxItem href="http://google.com">Google</MyListBoxItem>
187+
</ListBox>
188+
);
189+

packages/react-aria-components/test/ListBox.test.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,10 @@ describe('ListBox', () => {
288288
});
289289

290290
it('should support hover', async () => {
291-
let {getAllByRole} = renderListbox({selectionMode: 'multiple'}, {className: ({isHovered}) => isHovered ? 'hover' : ''});
291+
let hoverStartSpy = jest.fn();
292+
let hoverChangeSpy = jest.fn();
293+
let hoverEndSpy = jest.fn();
294+
let {getAllByRole} = renderListbox({selectionMode: 'multiple'}, {className: ({isHovered}) => isHovered ? 'hover' : '', onHoverStart: hoverStartSpy, onHoverEnd: hoverEndSpy, onHoverChange: hoverChangeSpy});
292295
let option = getAllByRole('option')[0];
293296

294297
expect(option).not.toHaveAttribute('data-hovered');
@@ -297,10 +300,14 @@ describe('ListBox', () => {
297300
await user.hover(option);
298301
expect(option).toHaveAttribute('data-hovered', 'true');
299302
expect(option).toHaveClass('hover');
303+
expect(hoverStartSpy).toHaveBeenCalledTimes(1);
304+
expect(hoverChangeSpy).toHaveBeenCalledTimes(1);
300305

301306
await user.unhover(option);
302307
expect(option).not.toHaveAttribute('data-hovered');
303308
expect(option).not.toHaveClass('hover');
309+
expect(hoverEndSpy).toHaveBeenCalledTimes(1);
310+
expect(hoverChangeSpy).toHaveBeenCalledTimes(2);
304311
});
305312

306313
it('should not show hover state when item is not interactive', async () => {

0 commit comments

Comments
 (0)