From c1d835318ce65543523aaa79eeb254bfa9570ffc Mon Sep 17 00:00:00 2001 From: Tooru Fujisawa Date: Fri, 9 Jan 2026 01:02:31 +0900 Subject: [PATCH] Make the entire list item clickable for the "Full Range" --- src/components/app/ProfileFilterNavigator.css | 4 +- src/components/app/ProfileFilterNavigator.tsx | 20 ++++-- src/components/shared/FilterNavigatorBar.css | 6 +- src/components/shared/FilterNavigatorBar.tsx | 65 ++++++++++++++----- .../components/FilterNavigatorBar.test.tsx | 17 +++++ .../FilterNavigatorBar.test.tsx.snap | 12 ++-- 6 files changed, 91 insertions(+), 33 deletions(-) diff --git a/src/components/app/ProfileFilterNavigator.css b/src/components/app/ProfileFilterNavigator.css index 9daf3b89da..cb035a2a8c 100644 --- a/src/components/app/ProfileFilterNavigator.css +++ b/src/components/app/ProfileFilterNavigator.css @@ -28,7 +28,7 @@ inset-inline-start: 4px; } -span.profileFilterNavigator--tab-selector::before { +.profileFilterNavigator--tab-selector:not(.button)::before { /* Disabled tab selector indicates this with a grayed out arrow. */ color: var(--grey-30); } @@ -38,7 +38,7 @@ span.profileFilterNavigator--tab-selector::before { color: currentcolor; } - span.profileFilterNavigator--tab-selector::before { + .profilefilternavigator--tab-selector:not(.button)::before { color: GrayText; } } diff --git a/src/components/app/ProfileFilterNavigator.tsx b/src/components/app/ProfileFilterNavigator.tsx index d4bb3873a8..936fd8003d 100644 --- a/src/components/app/ProfileFilterNavigator.tsx +++ b/src/components/app/ProfileFilterNavigator.tsx @@ -52,6 +52,10 @@ class ProfileFilterNavigatorBarImpl extends React.PureComponent { } ); + _onFirstItemClick = (event: React.MouseEvent) => { + this._showTabSelectorMenu(event); + }; + _showTabSelectorMenu = (event: React.MouseEvent) => { if (this.props.items.length > 0 || this.props.uncommittedItem) { // Do nothing if there are committed ranges. We only allow users to change @@ -82,6 +86,7 @@ class ProfileFilterNavigatorBarImpl extends React.PureComponent { } = this.props; let firstItem; + let isFirstItemClickable = false; if (pageDataByTabID && pageDataByTabID.size > 0) { const pageData = tabFilter !== null ? pageDataByTabID.get(tabFilter) : null; @@ -114,18 +119,18 @@ class ProfileFilterNavigatorBarImpl extends React.PureComponent { ); if (items.length === 0 && !uncommittedItem) { - // It should be a clickable button if there are no committed ranges. + // It should be clickable if there are no committed ranges. + isFirstItemClickable = true; firstItem = ( - + ); } else { // There are committed ranges, don't make it button because this will @@ -169,6 +174,9 @@ class ProfileFilterNavigatorBarImpl extends React.PureComponent { selectedItem={selectedItem} uncommittedItem={uncommittedItem} onPop={onPop} + onFirstItemClick={ + isFirstItemClickable ? this._onFirstItemClick : undefined + } /> {pageDataByTabID && pageDataByTabID.size > 0 ? ( diff --git a/src/components/shared/FilterNavigatorBar.css b/src/components/shared/FilterNavigatorBar.css index 2a41257b5c..bbe05642d6 100644 --- a/src/components/shared/FilterNavigatorBar.css +++ b/src/components/shared/FilterNavigatorBar.css @@ -122,7 +122,9 @@ } .filterNavigatorBarItem:not(.filterNavigatorBarLeafItem):hover, -.filterNavigatorBarItem:has(button.profileFilterNavigator--tab-selector):hover { +.filterNavigatorBarItem:has( + .profileFilterNavigator--tab-selector.button + ):hover { background-color: var(--internal-hover-background-color); color: var(--internal-hover-color); } @@ -168,7 +170,7 @@ /* When the tab selector is active, we want the item to look like a button */ .filterNavigatorBarSelectedItem:has( - button.profileFilterNavigator--tab-selector + .profileFilterNavigator--tab-selector.button ) { background-color: ButtonFace; color: ButtonText; diff --git a/src/components/shared/FilterNavigatorBar.tsx b/src/components/shared/FilterNavigatorBar.tsx index 12011ef64d..d539b820f0 100644 --- a/src/components/shared/FilterNavigatorBar.tsx +++ b/src/components/shared/FilterNavigatorBar.tsx @@ -7,7 +7,9 @@ import classNames from 'classnames'; import './FilterNavigatorBar.css'; type FilterNavigatorBarListItemProps = { - readonly onClick?: null | ((index: number) => unknown); + readonly onClick?: + | null + | ((index: number, event: React.MouseEvent) => unknown); readonly index: number; readonly isFirstItem: boolean; readonly isLastItem: boolean; @@ -18,10 +20,10 @@ type FilterNavigatorBarListItemProps = { }; class FilterNavigatorBarListItem extends React.PureComponent { - _onClick = () => { + _onClick = (event: React.MouseEvent) => { const { index, onClick } = this.props; if (onClick) { - onClick(index); + onClick(index, event); } }; @@ -61,29 +63,58 @@ type Props = { readonly className: string; readonly items: ReadonlyArray; readonly onPop: (param: number) => void; + readonly onFirstItemClick?: (event: React.MouseEvent) => void; readonly selectedItem: number; readonly uncommittedItem?: string; }; export class FilterNavigatorBar extends React.PureComponent { + _onPop = (index: number, _event: React.MouseEvent) => { + const { onPop } = this.props; + onPop(index); + }; + + _onFirstItemClick = (_: number, event: React.MouseEvent) => { + const { onFirstItemClick } = this.props; + if (onFirstItemClick) { + onFirstItemClick(event); + } + }; + override render() { - const { className, items, selectedItem, uncommittedItem, onPop } = - this.props; + const { + className, + items, + selectedItem, + uncommittedItem, + onFirstItemClick, + } = this.props; return (
    - {items.map((item, i) => ( - - {item} - - ))} + {items.map((item, i) => { + let onClick = null; + if (i === 0 && !uncommittedItem && onFirstItemClick) { + onClick = this._onFirstItemClick; + } else if (i === items.length - 1 && !uncommittedItem) { + onClick = null; + } else { + onClick = this._onPop; + } + + return ( + + {item} + + ); + })} {uncommittedItem ? ( { const { getByText } = setup(); expect(getByText(/Full Range/)).toBeInTheDocument(); }); + + it('opens a menu when the first item is clicked', () => { + const { getByText } = setup(); + + const item = getByText(/Full Range/).closest('.filterNavigatorBarItem'); + + const listener = jest.fn(); + window.addEventListener('REACT_CONTEXTMENU_SHOW', listener); + fireEvent.click(item!); + window.removeEventListener('REACT_CONTEXTMENU_SHOW', listener); + + expect(listener).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'REACT_CONTEXTMENU_SHOW', + }) + ); + }); }); diff --git a/src/test/components/__snapshots__/FilterNavigatorBar.test.tsx.snap b/src/test/components/__snapshots__/FilterNavigatorBar.test.tsx.snap index b1d6e6c631..e4643957e5 100644 --- a/src/test/components/__snapshots__/FilterNavigatorBar.test.tsx.snap +++ b/src/test/components/__snapshots__/FilterNavigatorBar.test.tsx.snap @@ -7,16 +7,16 @@ exports[`app/ProfileFilterNavigator renders ProfileFilterNavigator properly 1`]
  1. - - - + +
`;