Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions src/DropdownMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import Menu, { MenuItem } from '@rc-component/menu';
import * as React from 'react';
import Menu, { MenuItem, MenuRef } from '@rc-component/menu';
import React, { useEffect, useRef } from 'react';
import MentionsContext from './MentionsContext';
import type { DataDrivenOptionProps } from './Mentions';
interface DropdownMenuProps {
export interface DropdownMenuProps {
prefixCls?: string;
options: DataDrivenOptionProps[];
}
Expand All @@ -24,9 +24,26 @@ function DropdownMenu(props: DropdownMenuProps) {

const { prefixCls, options } = props;
const activeOption = options[activeIndex] || {};
const menuRef = useRef<MenuRef>(null);

// Monitor the changes in ActiveIndex and scroll to the visible area if there are any changes
useEffect(() => {
if (activeIndex === -1 || !menuRef.current) {
return;
}

const activeItem = menuRef.current?.findItem?.({ key: activeOption.key });
if (activeItem) {
activeItem.scrollIntoView({
block: 'nearest',
inline: 'nearest',
});
}
}, [activeIndex, activeOption.key]);

return (
<Menu
ref={menuRef}
prefixCls={`${prefixCls}-menu`}
activeKey={activeOption.key}
onSelect={({ key }) => {
Expand Down
52 changes: 52 additions & 0 deletions tests/DropdownMenu.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import Mentions, { UnstableContext } from '../src';
import { expectMeasuring } from './util';

describe('DropdownMenu', () => {
// Generate 20 options for testing scrolling behavior
const generateOptions = Array.from({ length: 20 }).map((_, index) => ({
value: `item-${index}`,
label: `item-${index}`,
}));

// Setup component with UnstableContext for testing dropdown behavior
const { container } = render(
<UnstableContext.Provider value={{ open: true }}>
<Mentions defaultValue="@" options={generateOptions} />
</UnstableContext.Provider>,
);

it('should scroll into view when navigating with keyboard', () => {
// Mock scrollIntoView since it's not implemented in JSDOM
const scrollIntoViewMock = jest
.spyOn(HTMLElement.prototype, 'scrollIntoView')
.mockImplementation(jest.fn());

// Press ArrowDown multiple times to make options overflow the visible area
for (let i = 0; i < 10; i++) {
fireEvent.keyDown(document.activeElement!, { key: 'ArrowDown' });
}

// Verify if scrollIntoView was called
expect(scrollIntoViewMock).toHaveBeenCalledWith({
block: 'nearest',
inline: 'nearest',
});

// Press ArrowUp to verify scrolling up
for (let i = 0; i < 5; i++) {
fireEvent.keyDown(document.activeElement!, { key: 'ArrowUp' });
}

// Verify if scrollIntoView was called again
expect(scrollIntoViewMock).toHaveBeenCalledWith({
block: 'nearest',
inline: 'nearest',
});

expectMeasuring(container);

scrollIntoViewMock.mockRestore();
});
});
2 changes: 2 additions & 0 deletions tests/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ global.ResizeObserver = class ResizeObserver {
unobserve() {}
disconnect() {}
};

window.HTMLElement.prototype.scrollIntoView = jest.fn();