Fix action menu dropdown (#8368)
Fix action menu dropdown not closing when clicking outside the table or board and introduce helper functions to get the action menu component ids.
This commit is contained in:
@ -5,6 +5,7 @@ import { RecordIndexActionMenuBarEntry } from '@/action-menu/components/RecordIn
|
||||
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { ActionBarHotkeyScope } from '@/action-menu/types/ActionBarHotKeyScope';
|
||||
import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId';
|
||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||
import { BottomBar } from '@/ui/layout/bottom-bar/components/BottomBar';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
@ -39,7 +40,7 @@ export const RecordIndexActionMenuBar = () => {
|
||||
|
||||
return (
|
||||
<BottomBar
|
||||
bottomBarId={`action-bar-${actionMenuId}`}
|
||||
bottomBarId={getActionBarIdFromActionMenuId(actionMenuId)}
|
||||
bottomBarHotkeyScopeFromParent={{
|
||||
scope: ActionBarHotkeyScope.ActionBar,
|
||||
}}
|
||||
|
||||
@ -7,6 +7,7 @@ import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionM
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState';
|
||||
import { ActionMenuDropdownHotkeyScope } from '@/action-menu/types/ActionMenuDropdownHotKeyScope';
|
||||
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
@ -47,14 +48,10 @@ export const RecordIndexActionMenuDropdown = () => {
|
||||
const actionMenuDropdownPosition = useRecoilValue(
|
||||
extractComponentState(
|
||||
recordIndexActionMenuDropdownPositionComponentState,
|
||||
`action-menu-dropdown-${actionMenuId}`,
|
||||
getActionMenuDropdownIdFromActionMenuId(actionMenuId),
|
||||
),
|
||||
);
|
||||
|
||||
if (actionMenuEntries.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//TODO: remove this
|
||||
const width = actionMenuEntries.some(
|
||||
(actionMenuEntry) => actionMenuEntry.label === 'Remove from favorites',
|
||||
@ -68,7 +65,7 @@ export const RecordIndexActionMenuDropdown = () => {
|
||||
className="action-menu-dropdown"
|
||||
>
|
||||
<Dropdown
|
||||
dropdownId={`action-menu-dropdown-${actionMenuId}`}
|
||||
dropdownId={getActionMenuDropdownIdFromActionMenuId(actionMenuId)}
|
||||
dropdownHotkeyScope={{
|
||||
scope: ActionMenuDropdownHotkeyScope.ActionMenuDropdown,
|
||||
}}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { useActionMenu } from '@/action-menu/hooks/useActionMenu';
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId';
|
||||
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
|
||||
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
|
||||
@ -27,13 +29,13 @@ export const RecordIndexActionMenuEffect = () => {
|
||||
// previous hotkey scope, and we don't want that here.
|
||||
const setIsBottomBarOpened = useSetRecoilComponentStateV2(
|
||||
isBottomBarOpenedComponentState,
|
||||
`action-bar-${actionMenuId}`,
|
||||
getActionBarIdFromActionMenuId(actionMenuId),
|
||||
);
|
||||
|
||||
const isDropdownOpen = useRecoilValue(
|
||||
extractComponentState(
|
||||
isDropdownOpenComponentState,
|
||||
`action-menu-dropdown-${actionMenuId}`,
|
||||
getActionMenuDropdownIdFromActionMenuId(actionMenuId),
|
||||
),
|
||||
);
|
||||
const { isRightDrawerOpen } = useRightDrawer();
|
||||
|
||||
@ -6,6 +6,7 @@ import { RecordIndexActionMenuBar } from '@/action-menu/components/RecordIndexAc
|
||||
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry';
|
||||
import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
@ -64,7 +65,7 @@ const meta: Meta<typeof RecordIndexActionMenuBar> = {
|
||||
|
||||
set(
|
||||
isBottomBarOpenedComponentState.atomFamily({
|
||||
instanceId: 'action-bar-story-action-menu',
|
||||
instanceId: getActionBarIdFromActionMenuId('story-action-menu'),
|
||||
}),
|
||||
true,
|
||||
);
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId';
|
||||
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { useActionMenu } from '../useActionMenu';
|
||||
@ -23,6 +25,9 @@ jest.mock('@/ui/layout/dropdown/hooks/useDropdownV2', () => ({
|
||||
|
||||
describe('useActionMenu', () => {
|
||||
const actionMenuId = 'test-action-menu';
|
||||
const actionBarId = getActionBarIdFromActionMenuId(actionMenuId);
|
||||
const actionMenuDropdownId =
|
||||
getActionMenuDropdownIdFromActionMenuId(actionMenuId);
|
||||
|
||||
it('should return the correct functions', () => {
|
||||
const { result } = renderHook(() => useActionMenu(actionMenuId));
|
||||
@ -40,10 +45,8 @@ describe('useActionMenu', () => {
|
||||
result.current.openActionMenuDropdown();
|
||||
});
|
||||
|
||||
expect(closeBottomBar).toHaveBeenCalledWith(`action-bar-${actionMenuId}`);
|
||||
expect(openDropdown).toHaveBeenCalledWith(
|
||||
`action-menu-dropdown-${actionMenuId}`,
|
||||
);
|
||||
expect(closeBottomBar).toHaveBeenCalledWith(actionBarId);
|
||||
expect(openDropdown).toHaveBeenCalledWith(actionMenuDropdownId);
|
||||
});
|
||||
|
||||
it('should call the correct functions when opening action bar', () => {
|
||||
@ -53,10 +56,8 @@ describe('useActionMenu', () => {
|
||||
result.current.openActionBar();
|
||||
});
|
||||
|
||||
expect(closeDropdown).toHaveBeenCalledWith(
|
||||
`action-menu-dropdown-${actionMenuId}`,
|
||||
);
|
||||
expect(openBottomBar).toHaveBeenCalledWith(`action-bar-${actionMenuId}`);
|
||||
expect(closeDropdown).toHaveBeenCalledWith(actionMenuDropdownId);
|
||||
expect(openBottomBar).toHaveBeenCalledWith(actionBarId);
|
||||
});
|
||||
|
||||
it('should call the correct function when closing action menu dropdown', () => {
|
||||
@ -66,9 +67,7 @@ describe('useActionMenu', () => {
|
||||
result.current.closeActionMenuDropdown();
|
||||
});
|
||||
|
||||
expect(closeDropdown).toHaveBeenCalledWith(
|
||||
`action-menu-dropdown-${actionMenuId}`,
|
||||
);
|
||||
expect(closeDropdown).toHaveBeenCalledWith(actionMenuDropdownId);
|
||||
});
|
||||
|
||||
it('should call the correct function when closing action bar', () => {
|
||||
@ -78,6 +77,6 @@ describe('useActionMenu', () => {
|
||||
result.current.closeActionBar();
|
||||
});
|
||||
|
||||
expect(closeBottomBar).toHaveBeenCalledWith(`action-bar-${actionMenuId}`);
|
||||
expect(closeBottomBar).toHaveBeenCalledWith(actionBarId);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId';
|
||||
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
|
||||
import { useBottomBar } from '@/ui/layout/bottom-bar/hooks/useBottomBar';
|
||||
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
|
||||
|
||||
@ -5,22 +7,26 @@ export const useActionMenu = (actionMenuId: string) => {
|
||||
const { openDropdown, closeDropdown } = useDropdownV2();
|
||||
const { openBottomBar, closeBottomBar } = useBottomBar();
|
||||
|
||||
const actionBarId = getActionBarIdFromActionMenuId(actionMenuId);
|
||||
const actionMenuDropdownId =
|
||||
getActionMenuDropdownIdFromActionMenuId(actionMenuId);
|
||||
|
||||
const openActionMenuDropdown = () => {
|
||||
closeBottomBar(`action-bar-${actionMenuId}`);
|
||||
openDropdown(`action-menu-dropdown-${actionMenuId}`);
|
||||
closeBottomBar(actionBarId);
|
||||
openDropdown(actionMenuDropdownId);
|
||||
};
|
||||
|
||||
const openActionBar = () => {
|
||||
closeDropdown(`action-menu-dropdown-${actionMenuId}`);
|
||||
openBottomBar(`action-bar-${actionMenuId}`);
|
||||
closeDropdown(actionMenuDropdownId);
|
||||
openBottomBar(actionBarId);
|
||||
};
|
||||
|
||||
const closeActionMenuDropdown = () => {
|
||||
closeDropdown(`action-menu-dropdown-${actionMenuId}`);
|
||||
closeDropdown(actionMenuDropdownId);
|
||||
};
|
||||
|
||||
const closeActionBar = () => {
|
||||
closeBottomBar(`action-bar-${actionMenuId}`);
|
||||
closeBottomBar(actionBarId);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId';
|
||||
|
||||
describe('getActionBarIdFromActionMenuId', () => {
|
||||
it('should return the correct action bar id', () => {
|
||||
expect(getActionBarIdFromActionMenuId('action-menu-id')).toBe(
|
||||
'action-bar-action-menu-id',
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,9 @@
|
||||
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
|
||||
|
||||
describe('getActionMenuDropdownIdFromActionMenuId', () => {
|
||||
it('should return the correct action menu dropdown id', () => {
|
||||
expect(getActionMenuDropdownIdFromActionMenuId('action-menu-id')).toBe(
|
||||
'action-menu-dropdown-action-menu-id',
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,9 @@
|
||||
import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId';
|
||||
|
||||
describe('getActionMenuIdFromRecordIndexId', () => {
|
||||
it('should return the correct action menu id', () => {
|
||||
expect(getActionMenuIdFromRecordIndexId('record-index-id')).toBe(
|
||||
'action-menu-record-index-record-index-id',
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,3 @@
|
||||
export const getActionBarIdFromActionMenuId = (actionMenuId: string) => {
|
||||
return `action-bar-${actionMenuId}`;
|
||||
};
|
||||
@ -0,0 +1,5 @@
|
||||
export const getActionMenuDropdownIdFromActionMenuId = (
|
||||
actionMenuId: string,
|
||||
) => {
|
||||
return `action-menu-dropdown-${actionMenuId}`;
|
||||
};
|
||||
@ -0,0 +1,3 @@
|
||||
export const getActionMenuIdFromRecordIndexId = (recordIndexId: string) => {
|
||||
return `action-menu-record-index-${recordIndexId}`;
|
||||
};
|
||||
Reference in New Issue
Block a user