512 Ability to navigate dropdown menus with keyboard (#11735)
# Ability to navigate dropdown menus with keyboard The aim of this PR is to improve accessibility by allowing the user to navigate inside the dropdown menus with the keyboard. This PR refactors the `SelectableList` and `SelectableListItem` components to move the Enter event handling responsibility from `SelectableList` to the individual `SelectableListItem` components. Closes [512](https://github.com/twentyhq/core-team-issues/issues/512) ## Key Changes: - All dropdowns are now navigable with arrow keys ## Technical Implementation: - Each `SelectableListItem` now has direct access to its own `Enter` key handler, improving component encapsulation - Removed the central `Enter` key handler logic from `SelectableList` - Added `SelectableList` and `SelectableListItem` to all `Dropdown` components inside the app - Updated all component implementations to adapt to the new pattern: - Action menu components (`ActionDropdownItem`, `ActionListItem`) - Command menu components - Object filter, sort and options dropdowns - Record picker components - Select components --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -1,5 +1,10 @@
|
||||
import { ActionDisplayProps } from '@/action-menu/actions/display/components/ActionDisplay';
|
||||
import { getActionLabel } from '@/action-menu/utils/getActionLabel';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
|
||||
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
@ -22,12 +27,25 @@ export const ActionDropdownItem = ({
|
||||
}
|
||||
};
|
||||
|
||||
const selectableListInstanceId = useAvailableComponentInstanceIdOrThrow(
|
||||
SelectableListComponentInstanceContext,
|
||||
);
|
||||
|
||||
const isSelected = useRecoilComponentFamilyValueV2(
|
||||
isSelectedItemIdComponentFamilySelector,
|
||||
action.key,
|
||||
selectableListInstanceId,
|
||||
);
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
key={action.key}
|
||||
LeftIcon={action.Icon}
|
||||
onClick={handleClick}
|
||||
text={getActionLabel(action.label)}
|
||||
/>
|
||||
<SelectableListItem itemId={action.key} onEnter={handleClick}>
|
||||
<MenuItem
|
||||
focused={isSelected}
|
||||
key={action.key}
|
||||
LeftIcon={action.Icon}
|
||||
onClick={handleClick}
|
||||
text={getActionLabel(action.label)}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
import { ActionDisplayProps } from '@/action-menu/actions/display/components/ActionDisplay';
|
||||
import { getActionLabel } from '@/action-menu/utils/getActionLabel';
|
||||
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
|
||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { useSelectableListListenToEnterHotkeyOnItem } from '@/ui/layout/selectable-list/hooks/useSelectableListListenToEnterHotkeyOnItem';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
@ -25,14 +23,8 @@ export const ActionListItem = ({
|
||||
}
|
||||
};
|
||||
|
||||
useSelectableListListenToEnterHotkeyOnItem({
|
||||
hotkeyScope: AppHotkeyScope.CommandMenuOpen,
|
||||
itemId: action.key,
|
||||
onEnter: handleClick,
|
||||
});
|
||||
|
||||
return (
|
||||
<SelectableItem itemId={action.key}>
|
||||
<SelectableListItem itemId={action.key} onEnter={handleClick}>
|
||||
<CommandMenuItem
|
||||
id={action.key}
|
||||
Icon={action.Icon}
|
||||
@ -42,6 +34,6 @@ export const ActionListItem = ({
|
||||
onClick={onClick}
|
||||
hotKeys={action.hotKeys}
|
||||
/>
|
||||
</SelectableItem>
|
||||
</SelectableListItem>
|
||||
);
|
||||
};
|
||||
|
||||
@ -114,6 +114,13 @@ export const AsDropdownItem: Story = {
|
||||
onClick: addToFavoritesMock,
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<SelectableListComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'story' }}
|
||||
>
|
||||
<Story />
|
||||
</SelectableListComponentInstanceContext.Provider>
|
||||
),
|
||||
(Story) => (
|
||||
<ActionMenuContext.Provider
|
||||
value={{
|
||||
|
||||
@ -2,6 +2,7 @@ import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-action
|
||||
import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey';
|
||||
import { createMockActionMenuActions } from '@/action-menu/mock/action-menu-actions.mock';
|
||||
import { getActionLabel } from '@/action-menu/utils/getActionLabel';
|
||||
import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
|
||||
import { expect } from '@storybook/jest';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { fn, userEvent, within } from '@storybook/test';
|
||||
@ -11,7 +12,17 @@ import { ActionDropdownItem } from '../ActionDropdownItem';
|
||||
const meta: Meta<typeof ActionDropdownItem> = {
|
||||
title: 'Modules/ActionMenu/Actions/Display/ActionDropdownItem',
|
||||
component: ActionDropdownItem,
|
||||
decorators: [ComponentDecorator, RouterDecorator],
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<SelectableListComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'story' }}
|
||||
>
|
||||
<Story />
|
||||
</SelectableListComponentInstanceContext.Provider>
|
||||
),
|
||||
ComponentDecorator,
|
||||
RouterDecorator,
|
||||
],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
@ -7,6 +7,8 @@ import { getRightDrawerActionMenuDropdownIdFromActionMenuId } from '@/action-men
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
@ -41,6 +43,16 @@ export const CommandMenuActionMenuDropdown = () => {
|
||||
[toggleDropdown],
|
||||
);
|
||||
|
||||
const recordSelectionActions = actions.filter(
|
||||
(action) => action.scope === ActionScope.RecordSelection,
|
||||
);
|
||||
|
||||
const selectableItemIdArray = recordSelectionActions.map(
|
||||
(action) => action.key,
|
||||
);
|
||||
|
||||
const { setSelectedItemId } = useSelectableList(actionMenuId);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
dropdownId={getRightDrawerActionMenuDropdownIdFromActionMenuId(
|
||||
@ -56,13 +68,22 @@ export const CommandMenuActionMenuDropdown = () => {
|
||||
}
|
||||
dropdownPlacement="top-end"
|
||||
dropdownOffset={{ y: parseInt(theme.spacing(2), 10) }}
|
||||
onOpen={() => {
|
||||
setSelectedItemId(selectableItemIdArray[0]);
|
||||
}}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
{actions
|
||||
.filter((action) => action.scope === ActionScope.RecordSelection)
|
||||
.map((action) => (
|
||||
<SelectableList
|
||||
selectableListInstanceId={actionMenuId}
|
||||
hotkeyScope={
|
||||
CommandMenuActionMenuDropdownHotkeyScope.CommandMenuActionMenuDropdown
|
||||
}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
>
|
||||
{recordSelectionActions.map((action) => (
|
||||
<ActionComponent action={action} key={action.key} />
|
||||
))}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
}
|
||||
/>
|
||||
|
||||
@ -9,8 +9,12 @@ import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/get
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
||||
import styled from '@emotion/styled';
|
||||
import { useContext } from 'react';
|
||||
@ -42,7 +46,7 @@ export const RecordIndexActionMenuDropdown = () => {
|
||||
);
|
||||
|
||||
const dropdownId = getActionMenuDropdownIdFromActionMenuId(actionMenuId);
|
||||
const { closeDropdown } = useDropdown(dropdownId);
|
||||
const { closeDropdown } = useDropdownV2();
|
||||
|
||||
const actionMenuDropdownPosition = useRecoilValue(
|
||||
extractComponentState(
|
||||
@ -53,6 +57,16 @@ export const RecordIndexActionMenuDropdown = () => {
|
||||
|
||||
const { openCommandMenu } = useCommandMenu();
|
||||
|
||||
const selectedItemIdArray = [
|
||||
...recordIndexActions.map((action) => action.key),
|
||||
'more-actions',
|
||||
];
|
||||
|
||||
const selectedItemId = useRecoilComponentValueV2(
|
||||
selectedItemIdComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
dropdownId={dropdownId}
|
||||
@ -69,18 +83,33 @@ export const RecordIndexActionMenuDropdown = () => {
|
||||
dropdownComponents={
|
||||
<StyledDropdownMenuContainer className="action-menu-dropdown">
|
||||
<DropdownMenuItemsContainer>
|
||||
{recordIndexActions.map((action) => (
|
||||
<ActionComponent action={action} key={action.key} />
|
||||
))}
|
||||
<MenuItem
|
||||
key="more-actions"
|
||||
LeftIcon={IconLayoutSidebarRightExpand}
|
||||
onClick={() => {
|
||||
closeDropdown();
|
||||
openCommandMenu();
|
||||
}}
|
||||
text="More actions"
|
||||
/>
|
||||
<SelectableList
|
||||
hotkeyScope={ActionMenuDropdownHotkeyScope.ActionMenuDropdown}
|
||||
selectableItemIdArray={selectedItemIdArray}
|
||||
selectableListInstanceId={dropdownId}
|
||||
>
|
||||
{recordIndexActions.map((action) => (
|
||||
<ActionComponent action={action} key={action.key} />
|
||||
))}
|
||||
<SelectableListItem
|
||||
itemId="more-actions"
|
||||
key="more-actions"
|
||||
onEnter={() => {
|
||||
closeDropdown(dropdownId);
|
||||
openCommandMenu();
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
LeftIcon={IconLayoutSidebarRightExpand}
|
||||
onClick={() => {
|
||||
closeDropdown(dropdownId);
|
||||
openCommandMenu();
|
||||
}}
|
||||
focused={selectedItemId === 'more-actions'}
|
||||
text="More actions"
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</StyledDropdownMenuContainer>
|
||||
}
|
||||
|
||||
@ -7,7 +7,6 @@ import { useMatchingCommandMenuActions } from '@/command-menu/hooks/useMatchingC
|
||||
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
|
||||
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
@ -89,9 +88,7 @@ export const CommandMenu = () => {
|
||||
>
|
||||
{isDefined(previousContextStoreCurrentObjectMetadataItemId) && (
|
||||
<CommandGroup heading={t`Context`}>
|
||||
<SelectableItem itemId={RESET_CONTEXT_TO_SELECTION}>
|
||||
<ResetContextToSelectionCommandButton />
|
||||
</SelectableItem>
|
||||
<ResetContextToSelectionCommandButton />
|
||||
</CommandGroup>
|
||||
)}
|
||||
</CommandMenuList>
|
||||
|
||||
@ -4,8 +4,6 @@ import { ActionGroupConfig } from '@/command-menu/components/CommandMenu';
|
||||
import { CommandMenuDefaultSelectionEffect } from '@/command-menu/components/CommandMenuDefaultSelectionEffect';
|
||||
import { COMMAND_MENU_SEARCH_BAR_HEIGHT } from '@/command-menu/constants/CommandMenuSearchBarHeight';
|
||||
import { COMMAND_MENU_SEARCH_BAR_PADDING } from '@/command-menu/constants/CommandMenuSearchBarPadding';
|
||||
import { RESET_CONTEXT_TO_SELECTION } from '@/command-menu/constants/ResetContextToSelection';
|
||||
import { useResetPreviousCommandMenuContext } from '@/command-menu/hooks/useResetPreviousCommandMenuContext';
|
||||
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
@ -64,9 +62,6 @@ export const CommandMenuList = ({
|
||||
loading = false,
|
||||
noResults = false,
|
||||
}: CommandMenuListProps) => {
|
||||
const { resetPreviousCommandMenuContext } =
|
||||
useResetPreviousCommandMenuContext();
|
||||
|
||||
const setHasUserSelectedCommand = useSetRecoilState(
|
||||
hasUserSelectedCommandState,
|
||||
);
|
||||
@ -82,12 +77,6 @@ export const CommandMenuList = ({
|
||||
selectableListInstanceId="command-menu-list"
|
||||
hotkeyScope={AppHotkeyScope.CommandMenuOpen}
|
||||
selectableItemIdArray={selectableItemIds}
|
||||
onEnter={(itemId) => {
|
||||
if (itemId === RESET_CONTEXT_TO_SELECTION) {
|
||||
resetPreviousCommandMenuContext();
|
||||
return;
|
||||
}
|
||||
}}
|
||||
onSelect={() => {
|
||||
setHasUserSelectedCommand(true);
|
||||
}}
|
||||
|
||||
@ -6,6 +6,7 @@ import { useResetPreviousCommandMenuContext } from '@/command-menu/hooks/useRese
|
||||
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
@ -42,17 +43,22 @@ export const ResetContextToSelectionCommandButton = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<CommandMenuItem
|
||||
id={RESET_CONTEXT_TO_SELECTION}
|
||||
Icon={IconArrowBackUp}
|
||||
label={t`Reset to`}
|
||||
RightComponent={
|
||||
<CommandMenuContextRecordsChip
|
||||
objectMetadataItemId={objectMetadataItem.id}
|
||||
instanceId={COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID}
|
||||
/>
|
||||
}
|
||||
onClick={resetPreviousCommandMenuContext}
|
||||
/>
|
||||
<SelectableListItem
|
||||
itemId={RESET_CONTEXT_TO_SELECTION}
|
||||
onEnter={resetPreviousCommandMenuContext}
|
||||
>
|
||||
<CommandMenuItem
|
||||
id={RESET_CONTEXT_TO_SELECTION}
|
||||
Icon={IconArrowBackUp}
|
||||
label={t`Reset to`}
|
||||
RightComponent={
|
||||
<CommandMenuContextRecordsChip
|
||||
objectMetadataItemId={objectMetadataItem.id}
|
||||
instanceId={COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID}
|
||||
/>
|
||||
}
|
||||
onClick={resetPreviousCommandMenuContext}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
);
|
||||
};
|
||||
|
||||
@ -8,6 +8,18 @@ import { commandMenuNavigationStackState } from '@/command-menu/states/commandMe
|
||||
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
|
||||
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
|
||||
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
||||
import { CommandMenuHotkeyScope } from '@/command-menu/types/CommandMenuHotkeyScope';
|
||||
|
||||
const mockGoBackToPreviousHotkeyScope = jest.fn();
|
||||
const mockSetHotkeyScopeAndMemorizePreviousScope = jest.fn();
|
||||
|
||||
jest.mock('@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope', () => ({
|
||||
usePreviousHotkeyScope: () => ({
|
||||
goBackToPreviousHotkeyScope: mockGoBackToPreviousHotkeyScope,
|
||||
setHotkeyScopeAndMemorizePreviousScope:
|
||||
mockSetHotkeyScopeAndMemorizePreviousScope,
|
||||
}),
|
||||
}));
|
||||
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<RecoilRoot>
|
||||
@ -47,6 +59,10 @@ const renderHooks = () => {
|
||||
};
|
||||
|
||||
describe('useCommandMenu', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should open and close the command menu', () => {
|
||||
const { result } = renderHooks();
|
||||
|
||||
@ -55,6 +71,12 @@ describe('useCommandMenu', () => {
|
||||
});
|
||||
|
||||
expect(result.current.isCommandMenuOpened).toBe(true);
|
||||
expect(mockSetHotkeyScopeAndMemorizePreviousScope).toHaveBeenCalledWith(
|
||||
CommandMenuHotkeyScope.CommandMenuFocused,
|
||||
{
|
||||
commandMenuOpen: true,
|
||||
},
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.commandMenu.closeCommandMenu();
|
||||
@ -73,6 +95,12 @@ describe('useCommandMenu', () => {
|
||||
});
|
||||
|
||||
expect(result.current.isCommandMenuOpened).toBe(true);
|
||||
expect(mockSetHotkeyScopeAndMemorizePreviousScope).toHaveBeenCalledWith(
|
||||
CommandMenuHotkeyScope.CommandMenuFocused,
|
||||
{
|
||||
commandMenuOpen: true,
|
||||
},
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.commandMenu.toggleCommandMenu();
|
||||
@ -80,4 +108,21 @@ describe('useCommandMenu', () => {
|
||||
|
||||
expect(result.current.isCommandMenuOpened).toBe(false);
|
||||
});
|
||||
|
||||
it('should call goBackToPreviousHotkeyScope when closing the command menu', () => {
|
||||
const { result } = renderHooks();
|
||||
|
||||
act(() => {
|
||||
result.current.commandMenu.openCommandMenu();
|
||||
});
|
||||
|
||||
expect(result.current.isCommandMenuOpened).toBe(true);
|
||||
expect(mockGoBackToPreviousHotkeyScope).not.toHaveBeenCalled();
|
||||
|
||||
act(() => {
|
||||
result.current.commandMenu.closeCommandMenu();
|
||||
});
|
||||
|
||||
expect(mockGoBackToPreviousHotkeyScope).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@ -23,7 +23,6 @@ import { IconList } from 'twenty-ui/display';
|
||||
const mockCloseDropdown = jest.fn();
|
||||
const mockResetContextStoreStates = jest.fn();
|
||||
const mockResetSelectedItem = jest.fn();
|
||||
const mockGoBackToPreviousHotkeyScope = jest.fn();
|
||||
const mockEmitRightDrawerCloseEvent = jest.fn();
|
||||
|
||||
jest.mock('@/ui/layout/dropdown/hooks/useDropdownV2', () => ({
|
||||
@ -44,12 +43,6 @@ jest.mock('@/ui/layout/selectable-list/hooks/useSelectableList', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope', () => ({
|
||||
usePreviousHotkeyScope: () => ({
|
||||
goBackToPreviousHotkeyScope: mockGoBackToPreviousHotkeyScope,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent', () => ({
|
||||
emitRightDrawerCloseEvent: () => {
|
||||
mockEmitRightDrawerCloseEvent();
|
||||
@ -231,7 +224,6 @@ describe('useCommandMenuCloseAnimationCompleteCleanup', () => {
|
||||
expect(mockCloseDropdown).toHaveBeenCalledTimes(1);
|
||||
expect(mockResetContextStoreStates).toHaveBeenCalledTimes(2);
|
||||
expect(mockResetSelectedItem).toHaveBeenCalledTimes(1);
|
||||
expect(mockGoBackToPreviousHotkeyScope).toHaveBeenCalledTimes(1);
|
||||
expect(mockEmitRightDrawerCloseEvent).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(mockCloseDropdown).toHaveBeenCalledWith(
|
||||
|
||||
@ -2,11 +2,13 @@ import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
|
||||
|
||||
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
|
||||
import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
|
||||
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
|
||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||
import { useCloseAnyOpenDropdown } from '@/ui/layout/dropdown/hooks/useCloseAnyOpenDropdown';
|
||||
import { isDragSelectionStartEnabledState } from '@/ui/utilities/drag-select/states/internal/isDragSelectionStartEnabledState';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { useCallback } from 'react';
|
||||
import { IconDotsVertical } from 'twenty-ui/display';
|
||||
import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState';
|
||||
@ -14,16 +16,26 @@ import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState';
|
||||
export const useCommandMenu = () => {
|
||||
const { navigateCommandMenu } = useNavigateCommandMenu();
|
||||
const { closeAnyOpenDropdown } = useCloseAnyOpenDropdown();
|
||||
const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope(
|
||||
COMMAND_MENU_COMPONENT_INSTANCE_ID,
|
||||
);
|
||||
|
||||
const closeCommandMenu = useRecoilCallback(
|
||||
({ set }) =>
|
||||
({ set, snapshot }) =>
|
||||
() => {
|
||||
set(isCommandMenuOpenedState, false);
|
||||
set(isCommandMenuClosingState, true);
|
||||
set(isDragSelectionStartEnabledState, true);
|
||||
closeAnyOpenDropdown();
|
||||
const isCommandMenuOpened = snapshot
|
||||
.getLoadable(isCommandMenuOpenedState)
|
||||
.getValue();
|
||||
|
||||
if (isCommandMenuOpened) {
|
||||
set(isCommandMenuOpenedState, false);
|
||||
set(isCommandMenuClosingState, true);
|
||||
set(isDragSelectionStartEnabledState, true);
|
||||
closeAnyOpenDropdown();
|
||||
goBackToPreviousHotkeyScope();
|
||||
}
|
||||
},
|
||||
[closeAnyOpenDropdown],
|
||||
[closeAnyOpenDropdown, goBackToPreviousHotkeyScope],
|
||||
);
|
||||
|
||||
const openCommandMenu = useCallback(() => {
|
||||
|
||||
@ -18,7 +18,6 @@ import { emitRightDrawerCloseEvent } from '@/ui/layout/right-drawer/utils/emitRi
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/code-action/constants/WorkflowServerlessFunctionTabListComponentId';
|
||||
import { WorkflowServerlessFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/code-action/types/WorkflowServerlessFunctionTabId';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
@ -26,10 +25,6 @@ import { useRecoilCallback } from 'recoil';
|
||||
export const useCommandMenuCloseAnimationCompleteCleanup = () => {
|
||||
const { resetSelectedItem } = useSelectableList('command-menu-list');
|
||||
|
||||
const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope(
|
||||
COMMAND_MENU_COMPONENT_INSTANCE_ID,
|
||||
);
|
||||
|
||||
const { resetContextStoreStates } = useResetContextStoreStates();
|
||||
|
||||
const { closeDropdown } = useDropdownV2();
|
||||
@ -56,7 +51,6 @@ export const useCommandMenuCloseAnimationCompleteCleanup = () => {
|
||||
set(commandMenuNavigationStackState, []);
|
||||
resetSelectedItem();
|
||||
set(hasUserSelectedCommandState, false);
|
||||
goBackToPreviousHotkeyScope();
|
||||
|
||||
emitRightDrawerCloseEvent();
|
||||
set(isCommandMenuClosingState, false);
|
||||
@ -81,12 +75,7 @@ export const useCommandMenuCloseAnimationCompleteCleanup = () => {
|
||||
);
|
||||
}
|
||||
},
|
||||
[
|
||||
closeDropdown,
|
||||
goBackToPreviousHotkeyScope,
|
||||
resetContextStoreStates,
|
||||
resetSelectedItem,
|
||||
],
|
||||
[closeDropdown, resetContextStoreStates, resetSelectedItem],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@ -1,17 +1,14 @@
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
|
||||
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
|
||||
|
||||
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
|
||||
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
@ -22,7 +19,6 @@ import { ObjectFilterDropdownFilterSelectMenuItemV2 } from '@/object-record/obje
|
||||
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
|
||||
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
|
||||
import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState';
|
||||
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
|
||||
import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField';
|
||||
import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
@ -37,8 +33,10 @@ export const AdvancedFilterFieldSelectMenu = ({
|
||||
}: AdvancedFilterFieldSelectMenuProps) => {
|
||||
const { recordIndexId } = useRecordIndexContextOrThrow();
|
||||
|
||||
const { closeAdvancedFilterFieldSelectDropdown } =
|
||||
useAdvancedFilterFieldSelectDropdown(recordFilterId);
|
||||
const {
|
||||
closeAdvancedFilterFieldSelectDropdown,
|
||||
advancedFilterFieldSelectDropdownId,
|
||||
} = useAdvancedFilterFieldSelectDropdown(recordFilterId);
|
||||
|
||||
const [objectFilterDropdownSearchInput] = useRecoilComponentStateV2(
|
||||
objectFilterDropdownSearchInputComponentState,
|
||||
@ -76,12 +74,10 @@ export const AdvancedFilterFieldSelectMenu = ({
|
||||
(fieldMetadataItem) => !visibleColumnsIds.includes(fieldMetadataItem.id),
|
||||
);
|
||||
|
||||
const selectableFieldMetadataItemIds = filterableFieldMetadataItems.map(
|
||||
(fieldMetadataItem) => fieldMetadataItem.id,
|
||||
const { resetSelectedItem } = useSelectableList(
|
||||
advancedFilterFieldSelectDropdownId,
|
||||
);
|
||||
|
||||
const { resetSelectedItem } = useSelectableList(OBJECT_FILTER_DROPDOWN_ID);
|
||||
|
||||
const { selectFieldUsedInAdvancedFilterDropdown } =
|
||||
useSelectFieldUsedInAdvancedFilterDropdown();
|
||||
|
||||
@ -98,18 +94,6 @@ export const AdvancedFilterFieldSelectMenu = ({
|
||||
fieldMetadataItemIdUsedInDropdownComponentState,
|
||||
);
|
||||
|
||||
const handleEnter = (fieldMetadataItemId: string) => {
|
||||
const selectedFieldMetadataItem = filterableFieldMetadataItems.find(
|
||||
(fieldMetadataItem) => fieldMetadataItem.id === fieldMetadataItemId,
|
||||
);
|
||||
|
||||
if (!isDefined(selectedFieldMetadataItem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
handleFieldMetadataItemSelect(selectedFieldMetadataItem);
|
||||
};
|
||||
|
||||
const handleFieldMetadataItemSelect = (
|
||||
selectedFieldMetadataItem: FieldMetadataItem,
|
||||
) => {
|
||||
@ -138,41 +122,55 @@ export const AdvancedFilterFieldSelectMenu = ({
|
||||
visibleColumnsFieldMetadataItems.length > 0 &&
|
||||
hiddenColumnsFieldMetadataItems.length > 0;
|
||||
|
||||
const selectableItemIdArray = [
|
||||
...visibleColumnsFieldMetadataItems.map(
|
||||
(fieldMetadataItem) => fieldMetadataItem.id,
|
||||
),
|
||||
...hiddenColumnsFieldMetadataItems.map(
|
||||
(fieldMetadataItem) => fieldMetadataItem.id,
|
||||
),
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<AdvancedFilterFieldSelectSearchInput />
|
||||
<SelectableList
|
||||
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
|
||||
selectableItemIdArray={selectableFieldMetadataItemIds}
|
||||
selectableListInstanceId={OBJECT_FILTER_DROPDOWN_ID}
|
||||
onEnter={handleEnter}
|
||||
hotkeyScope={advancedFilterFieldSelectDropdownId}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
selectableListInstanceId={advancedFilterFieldSelectDropdownId}
|
||||
>
|
||||
<DropdownMenuItemsContainer>
|
||||
{visibleColumnsFieldMetadataItems.map(
|
||||
(visibleFieldMetadataItem, index) => (
|
||||
<SelectableItem
|
||||
<SelectableListItem
|
||||
itemId={visibleFieldMetadataItem.id}
|
||||
key={`visible-select-filter-${index}`}
|
||||
onEnter={() => {
|
||||
handleFieldMetadataItemSelect(visibleFieldMetadataItem);
|
||||
}}
|
||||
>
|
||||
<ObjectFilterDropdownFilterSelectMenuItemV2
|
||||
fieldMetadataItemToSelect={visibleFieldMetadataItem}
|
||||
onClick={handleFieldMetadataItemSelect}
|
||||
/>
|
||||
</SelectableItem>
|
||||
</SelectableListItem>
|
||||
),
|
||||
)}
|
||||
{shouldShowSeparator && <DropdownMenuSeparator />}
|
||||
{hiddenColumnsFieldMetadataItems.map(
|
||||
(hiddenFieldMetadataItem, index) => (
|
||||
<SelectableItem
|
||||
<SelectableListItem
|
||||
itemId={hiddenFieldMetadataItem.id}
|
||||
key={`hidden-select-filter-${index}`}
|
||||
onEnter={() => {
|
||||
handleFieldMetadataItemSelect(hiddenFieldMetadataItem);
|
||||
}}
|
||||
>
|
||||
<ObjectFilterDropdownFilterSelectMenuItemV2
|
||||
fieldMetadataItemToSelect={hiddenFieldMetadataItem}
|
||||
onClick={handleFieldMetadataItemSelect}
|
||||
/>
|
||||
</SelectableItem>
|
||||
</SelectableListItem>
|
||||
),
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
|
||||
@ -11,6 +11,9 @@ import { SelectControl } from '@/ui/input/components/SelectControl';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import styled from '@emotion/styled';
|
||||
@ -85,6 +88,11 @@ export const AdvancedFilterRecordFilterOperandSelect = ({
|
||||
})
|
||||
: [];
|
||||
|
||||
const selectedItemId = useRecoilComponentValueV2(
|
||||
selectedItemIdComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
if (isDisabled === true) {
|
||||
return (
|
||||
<SelectControl
|
||||
@ -115,15 +123,31 @@ export const AdvancedFilterRecordFilterOperandSelect = ({
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
{operandsForFilterType.map((filterOperand, index) => (
|
||||
<MenuItem
|
||||
key={`select-filter-operand-${index}`}
|
||||
onClick={() => {
|
||||
handleOperandChange(filterOperand);
|
||||
}}
|
||||
text={getOperandLabel(filterOperand)}
|
||||
/>
|
||||
))}
|
||||
<SelectableList
|
||||
hotkeyScope={dropdownId}
|
||||
selectableItemIdArray={operandsForFilterType.map(
|
||||
(operand) => operand,
|
||||
)}
|
||||
selectableListInstanceId={dropdownId}
|
||||
>
|
||||
{operandsForFilterType.map((filterOperand, index) => (
|
||||
<SelectableListItem
|
||||
itemId={filterOperand}
|
||||
key={`select-filter-operand-${index}`}
|
||||
onEnter={() => {
|
||||
handleOperandChange(filterOperand);
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === filterOperand}
|
||||
onClick={() => {
|
||||
handleOperandChange(filterOperand);
|
||||
}}
|
||||
text={getOperandLabel(filterOperand)}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
))}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
}
|
||||
dropdownHotkeyScope={{ scope: dropdownId }}
|
||||
|
||||
@ -15,6 +15,9 @@ import { isCompositeFieldTypeSubFieldsFilterable } from '@/object-record/record-
|
||||
import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsCompositeFieldTypeConfigs';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { IconApps, IconChevronLeft, useIcons } from 'twenty-ui/display';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
@ -72,6 +75,14 @@ export const AdvancedFilterSubFieldSelectMenu = ({
|
||||
setObjectFilterDropdownIsSelectingCompositeField(false);
|
||||
};
|
||||
|
||||
const { advancedFilterFieldSelectDropdownId } =
|
||||
useAdvancedFilterFieldSelectDropdown(recordFilterId);
|
||||
|
||||
const selectedItemId = useRecoilComponentValueV2(
|
||||
selectedItemIdComponentState,
|
||||
advancedFilterFieldSelectDropdownId,
|
||||
);
|
||||
|
||||
if (!isDefined(objectFilterDropdownSubMenuFieldType)) {
|
||||
return null;
|
||||
}
|
||||
@ -86,6 +97,11 @@ export const AdvancedFilterSubFieldSelectMenu = ({
|
||||
fieldMetadataItemUsedInDropdown.type,
|
||||
);
|
||||
|
||||
const selectableItemIdArray = [
|
||||
'-1',
|
||||
...options.map((subFieldName) => subFieldName),
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader
|
||||
@ -99,35 +115,58 @@ export const AdvancedFilterSubFieldSelectMenu = ({
|
||||
{getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)}
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
key={`select-filter-${-1}`}
|
||||
testId={`select-filter-${-1}`}
|
||||
onClick={() => {
|
||||
handleSelectFilter(fieldMetadataItemUsedInDropdown);
|
||||
}}
|
||||
LeftIcon={IconApps}
|
||||
text={`Any ${getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)} field`}
|
||||
/>
|
||||
{subFieldsAreFilterable &&
|
||||
options.map((subFieldName, index) => (
|
||||
<SelectableList
|
||||
hotkeyScope={advancedFilterFieldSelectDropdownId}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
selectableListInstanceId={advancedFilterFieldSelectDropdownId}
|
||||
>
|
||||
<SelectableListItem
|
||||
itemId={'-1'}
|
||||
key={`select-filter-${-1}`}
|
||||
onEnter={() => {
|
||||
handleSelectFilter(fieldMetadataItemUsedInDropdown);
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
key={`select-filter-${index}`}
|
||||
testId={`select-filter-${index}`}
|
||||
focused={selectedItemId === '-1'}
|
||||
onClick={() => {
|
||||
if (isDefined(fieldMetadataItemUsedInDropdown)) {
|
||||
handleSelectFilter(fieldMetadataItemUsedInDropdown);
|
||||
}}
|
||||
LeftIcon={IconApps}
|
||||
text={`Any ${getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)} field`}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
{subFieldsAreFilterable &&
|
||||
options.map((subFieldName, index) => (
|
||||
<SelectableListItem
|
||||
itemId={subFieldName}
|
||||
key={`select-filter-${index}`}
|
||||
onEnter={() => {
|
||||
handleSelectFilter(
|
||||
fieldMetadataItemUsedInDropdown,
|
||||
subFieldName,
|
||||
);
|
||||
}
|
||||
}}
|
||||
text={getCompositeSubFieldLabel(
|
||||
objectFilterDropdownSubMenuFieldType,
|
||||
subFieldName,
|
||||
)}
|
||||
LeftIcon={getIcon(fieldMetadataItemUsedInDropdown?.icon)}
|
||||
/>
|
||||
))}
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === subFieldName}
|
||||
key={`select-filter-${index}`}
|
||||
testId={`select-filter-${index}`}
|
||||
onClick={() => {
|
||||
handleSelectFilter(
|
||||
fieldMetadataItemUsedInDropdown,
|
||||
subFieldName,
|
||||
);
|
||||
}}
|
||||
text={getCompositeSubFieldLabel(
|
||||
objectFilterDropdownSubMenuFieldType,
|
||||
subFieldName,
|
||||
)}
|
||||
LeftIcon={getIcon(fieldMetadataItemUsedInDropdown?.icon)}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
))}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -94,9 +94,6 @@ export const ObjectFilterDropdownBooleanSelect = () => {
|
||||
selectableListInstanceId="boolean-select"
|
||||
selectableItemIdArray={options.map((option) => option.toString())}
|
||||
hotkeyScope={SingleRecordPickerHotkeyScope.SingleRecordPicker}
|
||||
onEnter={(itemId) => {
|
||||
handleOptionSelect(itemId === 'true');
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{options.map((option) => (
|
||||
|
||||
@ -11,14 +11,9 @@ import { objectFilterDropdownSearchInputComponentState } from '@/object-record/o
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
|
||||
import { useSelectFilterUsedInDropdown } from '@/object-record/object-filter-dropdown/hooks/useSelectFilterUsedInDropdown';
|
||||
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
|
||||
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
|
||||
import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
@ -96,36 +91,6 @@ export const ObjectFilterDropdownFilterSelect = ({
|
||||
(fieldMetadataItem) => !visibleColumnsIds.includes(fieldMetadataItem.id),
|
||||
);
|
||||
|
||||
const selectableFieldMetadataItemIds = filterableFieldMetadataItems.map(
|
||||
(fieldMetadataItem) => fieldMetadataItem.id,
|
||||
);
|
||||
|
||||
const { selectFilterUsedInDropdown } = useSelectFilterUsedInDropdown();
|
||||
|
||||
const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2(
|
||||
fieldMetadataItemIdUsedInDropdownComponentState,
|
||||
);
|
||||
|
||||
const { resetSelectedItem } = useSelectableList(OBJECT_FILTER_DROPDOWN_ID);
|
||||
|
||||
const handleEnter = (fieldMetadataItemId: string) => {
|
||||
const selectedFieldMetadataItem = filterableFieldMetadataItems.find(
|
||||
(fieldMetadataItem) => fieldMetadataItem.id === fieldMetadataItemId,
|
||||
);
|
||||
|
||||
if (!isDefined(selectedFieldMetadataItem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
resetSelectedItem();
|
||||
|
||||
selectFilterUsedInDropdown({
|
||||
fieldMetadataItemId,
|
||||
});
|
||||
|
||||
setFieldMetadataItemIdUsedInDropdown(fieldMetadataItemId);
|
||||
};
|
||||
|
||||
const shouldShowSeparator =
|
||||
visibleColumnsFieldMetadataItems.length > 0 &&
|
||||
hiddenColumnsFieldMetadataItems.length > 0;
|
||||
@ -137,6 +102,15 @@ export const ObjectFilterDropdownFilterSelect = ({
|
||||
|
||||
const { t } = useLingui();
|
||||
|
||||
const selectableFieldMetadataItemIds = [
|
||||
...visibleColumnsFieldMetadataItems.map(
|
||||
(fieldMetadataItem) => fieldMetadataItem.id,
|
||||
),
|
||||
...hiddenColumnsFieldMetadataItems.map(
|
||||
(fieldMetadataItem) => fieldMetadataItem.id,
|
||||
),
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledInput
|
||||
@ -151,34 +125,21 @@ export const ObjectFilterDropdownFilterSelect = ({
|
||||
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
|
||||
selectableItemIdArray={selectableFieldMetadataItemIds}
|
||||
selectableListInstanceId={OBJECT_FILTER_DROPDOWN_ID}
|
||||
onEnter={handleEnter}
|
||||
>
|
||||
<DropdownMenuItemsContainer>
|
||||
{visibleColumnsFieldMetadataItems.map(
|
||||
(visibleFieldMetadataItem, index) => (
|
||||
<SelectableItem
|
||||
itemId={visibleFieldMetadataItem.id}
|
||||
key={`visible-select-filter-${index}`}
|
||||
>
|
||||
<ObjectFilterDropdownFilterSelectMenuItem
|
||||
fieldMetadataItemToSelect={visibleFieldMetadataItem}
|
||||
/>
|
||||
</SelectableItem>
|
||||
),
|
||||
)}
|
||||
{visibleColumnsFieldMetadataItems.map((visibleFieldMetadataItem) => (
|
||||
<ObjectFilterDropdownFilterSelectMenuItem
|
||||
key={visibleFieldMetadataItem.id}
|
||||
fieldMetadataItemToSelect={visibleFieldMetadataItem}
|
||||
/>
|
||||
))}
|
||||
{shouldShowSeparator && <DropdownMenuSeparator />}
|
||||
{hiddenColumnsFieldMetadataItems.map(
|
||||
(hiddenFieldMetadataItem, index) => (
|
||||
<SelectableItem
|
||||
itemId={hiddenFieldMetadataItem.id}
|
||||
key={`hidden-select-filter-${index}`}
|
||||
>
|
||||
<ObjectFilterDropdownFilterSelectMenuItem
|
||||
fieldMetadataItemToSelect={hiddenFieldMetadataItem}
|
||||
/>
|
||||
</SelectableItem>
|
||||
),
|
||||
)}
|
||||
{hiddenColumnsFieldMetadataItems.map((hiddenFieldMetadataItem) => (
|
||||
<ObjectFilterDropdownFilterSelectMenuItem
|
||||
key={hiddenFieldMetadataItem.id}
|
||||
fieldMetadataItemToSelect={hiddenFieldMetadataItem}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</SelectableList>
|
||||
{shouldShowAdvancedFilterButton && <AdvancedFilterButton />}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId';
|
||||
import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
|
||||
@ -9,6 +10,7 @@ import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-rec
|
||||
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
|
||||
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
|
||||
import { getCompositeSubFieldLabel } from '@/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel';
|
||||
import { getFilterableFieldTypeLabel } from '@/object-record/object-filter-dropdown/utils/getFilterableFieldTypeLabel';
|
||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||
@ -19,6 +21,9 @@ import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/con
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
@ -130,6 +135,10 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => {
|
||||
setObjectFilterDropdownFilterIsSelected(false);
|
||||
setSubFieldNameUsedInDropdown(null);
|
||||
};
|
||||
const selectedItemId = useRecoilComponentValueV2(
|
||||
selectedItemIdComponentState,
|
||||
OBJECT_FILTER_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
if (!isDefined(objectFilterDropdownSubMenuFieldType)) {
|
||||
return null;
|
||||
@ -170,35 +179,65 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => {
|
||||
}
|
||||
/> */}
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
key={`select-filter-${-1}`}
|
||||
testId={`select-filter-${-1}`}
|
||||
onClick={() => {
|
||||
handleSelectFilter(fieldMetadataItemUsedInDropdown);
|
||||
}}
|
||||
LeftIcon={IconApps}
|
||||
text={`Any ${getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)} field`}
|
||||
/>
|
||||
{subFieldsAreFilterable &&
|
||||
options.map((subFieldName, index) => (
|
||||
<SelectableList
|
||||
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
|
||||
selectableItemIdArray={['-1', ...options]}
|
||||
selectableListInstanceId={OBJECT_FILTER_DROPDOWN_ID}
|
||||
>
|
||||
<SelectableListItem
|
||||
itemId={'-1'}
|
||||
key={`select-filter-${-1}`}
|
||||
onEnter={() => {
|
||||
handleSelectFilter(fieldMetadataItemUsedInDropdown);
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
key={`select-filter-${index}`}
|
||||
testId={`select-filter-${index}`}
|
||||
focused={selectedItemId === '-1'}
|
||||
key={`select-filter-${-1}`}
|
||||
testId={`select-filter-${-1}`}
|
||||
onClick={() => {
|
||||
if (isDefined(fieldMetadataItemUsedInDropdown)) {
|
||||
handleSelectFilter(fieldMetadataItemUsedInDropdown);
|
||||
}}
|
||||
LeftIcon={IconApps}
|
||||
text={`Any ${getFilterableFieldTypeLabel(
|
||||
objectFilterDropdownSubMenuFieldType,
|
||||
)} field`}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
|
||||
{subFieldsAreFilterable &&
|
||||
options.map((subFieldName, index) => (
|
||||
<SelectableListItem
|
||||
itemId={subFieldName}
|
||||
key={`select-filter-${index}`}
|
||||
onEnter={() => {
|
||||
handleSelectFilter(
|
||||
fieldMetadataItemUsedInDropdown,
|
||||
subFieldName,
|
||||
);
|
||||
}
|
||||
}}
|
||||
text={getCompositeSubFieldLabel(
|
||||
objectFilterDropdownSubMenuFieldType,
|
||||
subFieldName,
|
||||
)}
|
||||
LeftIcon={getIcon(fieldMetadataItemUsedInDropdown?.icon)}
|
||||
/>
|
||||
))}
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === subFieldName}
|
||||
key={`select-filter-${index}`}
|
||||
testId={`select-filter-${index}`}
|
||||
onClick={() => {
|
||||
if (isDefined(fieldMetadataItemUsedInDropdown)) {
|
||||
handleSelectFilter(
|
||||
fieldMetadataItemUsedInDropdown,
|
||||
subFieldName,
|
||||
);
|
||||
}
|
||||
}}
|
||||
text={getCompositeSubFieldLabel(
|
||||
objectFilterDropdownSubMenuFieldType,
|
||||
subFieldName,
|
||||
)}
|
||||
LeftIcon={getIcon(fieldMetadataItemUsedInDropdown?.icon)}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
))}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -14,6 +14,7 @@ import { currentRecordFiltersComponentState } from '@/object-record/record-filte
|
||||
import { findDuplicateRecordFilterInNonAdvancedRecordFilters } from '@/object-record/record-filter/utils/findDuplicateRecordFilterInNonAdvancedRecordFilters';
|
||||
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
|
||||
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
@ -23,7 +24,7 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
import { MenuItemSelect } from 'twenty-ui/navigation';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
|
||||
export type ObjectFilterDropdownFilterSelectMenuItemProps = {
|
||||
fieldMetadataItemToSelect: FieldMetadataItem;
|
||||
@ -132,13 +133,17 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<MenuItemSelect
|
||||
selected={false}
|
||||
hovered={isSelectedItem}
|
||||
onClick={handleClick}
|
||||
LeftIcon={Icon}
|
||||
text={fieldMetadataItemToSelect.label}
|
||||
hasSubMenu={shouldShowSubMenu}
|
||||
/>
|
||||
<SelectableListItem
|
||||
itemId={fieldMetadataItemToSelect.id}
|
||||
onEnter={handleClick}
|
||||
>
|
||||
<MenuItem
|
||||
focused={isSelectedItem}
|
||||
onClick={handleClick}
|
||||
LeftIcon={Icon}
|
||||
text={fieldMetadataItemToSelect.label}
|
||||
hasSubMenu={shouldShowSubMenu}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
);
|
||||
};
|
||||
|
||||
@ -6,7 +6,7 @@ import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectab
|
||||
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
import { MenuItemSelect } from 'twenty-ui/navigation';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
|
||||
export type ObjectFilterDropdownFilterSelectMenuItemV2Props = {
|
||||
fieldMetadataItemToSelect: FieldMetadataItem;
|
||||
@ -37,9 +37,8 @@ export const ObjectFilterDropdownFilterSelectMenuItemV2 = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<MenuItemSelect
|
||||
selected={false}
|
||||
hovered={isSelectedItem}
|
||||
<MenuItem
|
||||
focused={isSelectedItem}
|
||||
onClick={handleClick}
|
||||
LeftIcon={Icon}
|
||||
text={fieldMetadataItemToSelect.label}
|
||||
|
||||
@ -167,12 +167,6 @@ export const ObjectFilterDropdownOptionSelect = () => {
|
||||
selectableListInstanceId={componentInstanceId}
|
||||
selectableItemIdArray={objectRecordsIds}
|
||||
hotkeyScope={SingleRecordPickerHotkeyScope.SingleRecordPicker}
|
||||
onEnter={(itemId) => {
|
||||
const option = optionsInDropdown.find((option) => option.id === itemId);
|
||||
if (isDefined(option)) {
|
||||
handleMultipleOptionSelectChange(option, !option.isSelected);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{optionsInDropdown?.map((option) => (
|
||||
|
||||
@ -1,13 +1,18 @@
|
||||
import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId';
|
||||
import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
|
||||
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
|
||||
import { useSetViewTypeFromLayoutOptionsMenu } from '@/object-record/object-options-dropdown/hooks/useSetViewTypeFromLayoutOptionsMenu';
|
||||
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
||||
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
|
||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
|
||||
@ -74,6 +79,18 @@ export const ObjectOptionsDropdownLayoutContent = () => {
|
||||
const isDefaultView = currentView?.key === 'INDEX';
|
||||
const nbsp = '\u00A0';
|
||||
|
||||
const selectableItemIdArray = [
|
||||
ViewType.Table,
|
||||
...(isDefaultView ? [] : [ViewType.Kanban]),
|
||||
ViewOpenRecordInType.SIDE_PANEL,
|
||||
...(currentView?.type === ViewType.Kanban ? ['Group', 'Compact view'] : []),
|
||||
];
|
||||
|
||||
const selectedItemId = useRecoilComponentValueV2(
|
||||
selectedItemIdComponentState,
|
||||
OBJECT_OPTIONS_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader
|
||||
@ -86,81 +103,132 @@ export const ObjectOptionsDropdownLayoutContent = () => {
|
||||
>
|
||||
{t`Layout`}
|
||||
</DropdownMenuHeader>
|
||||
|
||||
{!!currentView && (
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItemSelect
|
||||
LeftIcon={IconTable}
|
||||
text={t`Table`}
|
||||
selected={currentView?.type === ViewType.Table}
|
||||
onClick={async () => {
|
||||
if (currentView?.type !== ViewType.Table) {
|
||||
await setAndPersistViewType(ViewType.Table);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<MenuItemSelect
|
||||
LeftIcon={viewTypeIconMapping(ViewType.Kanban)}
|
||||
text={t`Kanban`}
|
||||
disabled={isDefaultView}
|
||||
contextualText={
|
||||
isDefaultView ? (
|
||||
<>
|
||||
{nbsp}·{nbsp}
|
||||
<OverflowingTextWithTooltip
|
||||
text={t`Not available for default view`}
|
||||
/>
|
||||
</>
|
||||
) : availableFieldsForKanban.length === 0 ? (
|
||||
t`Create Select...`
|
||||
) : undefined
|
||||
}
|
||||
selected={currentView?.type === ViewType.Kanban}
|
||||
onClick={handleSelectKanbanViewType}
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<MenuItem
|
||||
onClick={() => onContentChange('layoutOpenIn')}
|
||||
LeftIcon={
|
||||
recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL
|
||||
? IconLayoutSidebarRight
|
||||
: IconLayoutNavbar
|
||||
}
|
||||
text={t`Open in`}
|
||||
contextualText={
|
||||
recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL
|
||||
? t`Side Panel`
|
||||
: t`Record Page`
|
||||
}
|
||||
hasSubMenu
|
||||
/>
|
||||
{currentView?.type === ViewType.Kanban && (
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={() =>
|
||||
isDefined(recordGroupFieldMetadata)
|
||||
? onContentChange('recordGroups')
|
||||
: onContentChange('recordGroupFields')
|
||||
<SelectableList
|
||||
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
|
||||
hotkeyScope={TableOptionsHotkeyScope.Dropdown}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
>
|
||||
<SelectableListItem
|
||||
itemId={ViewType.Table}
|
||||
onEnter={() => {
|
||||
setAndPersistViewType(ViewType.Table);
|
||||
}}
|
||||
>
|
||||
<MenuItemSelect
|
||||
LeftIcon={IconTable}
|
||||
text={t`Table`}
|
||||
selected={currentView?.type === ViewType.Table}
|
||||
focused={selectedItemId === ViewType.Table}
|
||||
onClick={async () => {
|
||||
if (currentView?.type !== ViewType.Table) {
|
||||
await setAndPersistViewType(ViewType.Table);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
<SelectableListItem
|
||||
itemId={ViewType.Kanban}
|
||||
onEnter={() => {
|
||||
setAndPersistViewType(ViewType.Kanban);
|
||||
}}
|
||||
>
|
||||
<MenuItemSelect
|
||||
LeftIcon={viewTypeIconMapping(ViewType.Kanban)}
|
||||
text={t`Kanban`}
|
||||
disabled={isDefaultView}
|
||||
focused={selectedItemId === ViewType.Kanban}
|
||||
contextualText={
|
||||
isDefaultView ? (
|
||||
<>
|
||||
{nbsp}·{nbsp}
|
||||
<OverflowingTextWithTooltip
|
||||
text={t`Not available for default view`}
|
||||
/>
|
||||
</>
|
||||
) : availableFieldsForKanban.length === 0 ? (
|
||||
t`Create Select...`
|
||||
) : undefined
|
||||
}
|
||||
selected={currentView?.type === ViewType.Kanban}
|
||||
onClick={handleSelectKanbanViewType}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
<DropdownMenuSeparator />
|
||||
<SelectableListItem
|
||||
itemId={ViewOpenRecordInType.SIDE_PANEL}
|
||||
onEnter={() => {
|
||||
onContentChange('layoutOpenIn');
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === ViewOpenRecordInType.SIDE_PANEL}
|
||||
LeftIcon={
|
||||
recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL
|
||||
? IconLayoutSidebarRight
|
||||
: IconLayoutNavbar
|
||||
}
|
||||
text={t`Open in`}
|
||||
contextualText={
|
||||
recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL
|
||||
? t`Side Panel`
|
||||
: t`Record Page`
|
||||
}
|
||||
LeftIcon={IconLayoutList}
|
||||
text={t`Group`}
|
||||
contextualText={recordGroupFieldMetadata?.label}
|
||||
hasSubMenu
|
||||
/>
|
||||
</SelectableListItem>
|
||||
{currentView?.type === ViewType.Kanban && (
|
||||
<>
|
||||
<SelectableListItem
|
||||
itemId={'Group'}
|
||||
onEnter={() => {
|
||||
isDefined(recordGroupFieldMetadata)
|
||||
? onContentChange('recordGroups')
|
||||
: onContentChange('recordGroupFields');
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === 'Group'}
|
||||
onClick={() =>
|
||||
isDefined(recordGroupFieldMetadata)
|
||||
? onContentChange('recordGroups')
|
||||
: onContentChange('recordGroupFields')
|
||||
}
|
||||
LeftIcon={IconLayoutList}
|
||||
text={t`Group`}
|
||||
contextualText={recordGroupFieldMetadata?.label}
|
||||
hasSubMenu
|
||||
/>
|
||||
</SelectableListItem>
|
||||
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconBaselineDensitySmall}
|
||||
onToggleChange={() =>
|
||||
setAndPersistIsCompactModeActive(
|
||||
!isCompactModeActive,
|
||||
currentView,
|
||||
)
|
||||
}
|
||||
toggled={isCompactModeActive}
|
||||
text={t`Compact view`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<SelectableListItem
|
||||
itemId={'Compact view'}
|
||||
onEnter={() => {
|
||||
setAndPersistIsCompactModeActive(
|
||||
!isCompactModeActive,
|
||||
currentView,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<MenuItemToggle
|
||||
focused={selectedItemId === 'Compact view'}
|
||||
LeftIcon={IconBaselineDensitySmall}
|
||||
onToggleChange={() =>
|
||||
setAndPersistIsCompactModeActive(
|
||||
!isCompactModeActive,
|
||||
currentView,
|
||||
)
|
||||
}
|
||||
toggled={isCompactModeActive}
|
||||
text={t`Compact view`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</>
|
||||
)}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
)}
|
||||
</>
|
||||
|
||||
@ -1,9 +1,15 @@
|
||||
import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId';
|
||||
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
|
||||
import { useUpdateObjectViewOptions } from '@/object-record/object-options-dropdown/hooks/useUpdateObjectViewOptions';
|
||||
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
|
||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
|
||||
import { t } from '@lingui/core/macro';
|
||||
@ -21,6 +27,16 @@ export const ObjectOptionsDropdownLayoutOpenInContent = () => {
|
||||
const { currentView } = useGetCurrentViewOnly();
|
||||
const { setAndPersistOpenRecordIn } = useUpdateObjectViewOptions();
|
||||
|
||||
const selectedItemId = useRecoilComponentValueV2(
|
||||
selectedItemIdComponentState,
|
||||
OBJECT_OPTIONS_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
const selectableItemIdArray = [
|
||||
ViewOpenRecordInType.SIDE_PANEL,
|
||||
ViewOpenRecordInType.RECORD_PAGE,
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader
|
||||
@ -34,30 +50,60 @@ export const ObjectOptionsDropdownLayoutOpenInContent = () => {
|
||||
{t`Open in`}
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItemSelect
|
||||
LeftIcon={IconLayoutSidebarRight}
|
||||
text={t`Side Panel`}
|
||||
selected={recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL}
|
||||
onClick={() =>
|
||||
setAndPersistOpenRecordIn(
|
||||
ViewOpenRecordInType.SIDE_PANEL,
|
||||
currentView,
|
||||
)
|
||||
}
|
||||
/>
|
||||
<MenuItemSelect
|
||||
LeftIcon={IconLayoutNavbar}
|
||||
text={t`Record Page`}
|
||||
selected={
|
||||
recordIndexOpenRecordIn === ViewOpenRecordInType.RECORD_PAGE
|
||||
}
|
||||
onClick={() =>
|
||||
setAndPersistOpenRecordIn(
|
||||
ViewOpenRecordInType.RECORD_PAGE,
|
||||
currentView,
|
||||
)
|
||||
}
|
||||
/>
|
||||
<SelectableList
|
||||
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
|
||||
hotkeyScope={TableOptionsHotkeyScope.Dropdown}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
>
|
||||
<SelectableListItem
|
||||
itemId={ViewOpenRecordInType.SIDE_PANEL}
|
||||
onEnter={() =>
|
||||
setAndPersistOpenRecordIn(
|
||||
ViewOpenRecordInType.SIDE_PANEL,
|
||||
currentView,
|
||||
)
|
||||
}
|
||||
>
|
||||
<MenuItemSelect
|
||||
LeftIcon={IconLayoutSidebarRight}
|
||||
text={t`Side Panel`}
|
||||
selected={
|
||||
recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL
|
||||
}
|
||||
focused={selectedItemId === ViewOpenRecordInType.SIDE_PANEL}
|
||||
onClick={() =>
|
||||
setAndPersistOpenRecordIn(
|
||||
ViewOpenRecordInType.SIDE_PANEL,
|
||||
currentView,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
<SelectableListItem
|
||||
itemId={ViewOpenRecordInType.RECORD_PAGE}
|
||||
onEnter={() =>
|
||||
setAndPersistOpenRecordIn(
|
||||
ViewOpenRecordInType.RECORD_PAGE,
|
||||
currentView,
|
||||
)
|
||||
}
|
||||
>
|
||||
<MenuItemSelect
|
||||
LeftIcon={IconLayoutNavbar}
|
||||
text={t`Record Page`}
|
||||
selected={
|
||||
recordIndexOpenRecordIn === ViewOpenRecordInType.RECORD_PAGE
|
||||
}
|
||||
onClick={() =>
|
||||
setAndPersistOpenRecordIn(
|
||||
ViewOpenRecordInType.RECORD_PAGE,
|
||||
currentView,
|
||||
)
|
||||
}
|
||||
focused={selectedItemId === ViewOpenRecordInType.RECORD_PAGE}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { ObjectOptionsDropdownMenuViewName } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuViewName';
|
||||
import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId';
|
||||
import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
|
||||
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
|
||||
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
||||
@ -9,6 +10,9 @@ import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/Snac
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
@ -75,92 +79,155 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
||||
|
||||
const isDefaultView = currentView?.key === 'INDEX';
|
||||
|
||||
const selectableItemIdArray = [
|
||||
'Layout',
|
||||
'Fields',
|
||||
...(isDefaultView ? [] : ['Group']),
|
||||
'Copy link to view',
|
||||
...(isDefaultView ? [] : ['Delete view']),
|
||||
];
|
||||
|
||||
const selectedItemId = useRecoilComponentValueV2(
|
||||
selectedItemIdComponentState,
|
||||
OBJECT_OPTIONS_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{currentView && (
|
||||
<ObjectOptionsDropdownMenuViewName currentView={currentView} />
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<MenuItem
|
||||
onClick={() => onContentChange('layout')}
|
||||
LeftIcon={viewTypeIconMapping(currentView?.type ?? ViewType.Table)}
|
||||
text={t`Layout`}
|
||||
contextualText={`${capitalize(currentView?.type ?? '')}`}
|
||||
hasSubMenu
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<MenuItem
|
||||
onClick={() => onContentChange('fields')}
|
||||
LeftIcon={IconListDetails}
|
||||
text={t`Fields`}
|
||||
contextualText={`${visibleBoardFields.length} shown`}
|
||||
hasSubMenu
|
||||
/>
|
||||
|
||||
<div id="group-by-menu-item">
|
||||
<MenuItem
|
||||
onClick={() =>
|
||||
isDefined(recordGroupFieldMetadata)
|
||||
? onContentChange('recordGroups')
|
||||
: onContentChange('recordGroupFields')
|
||||
}
|
||||
LeftIcon={IconLayoutList}
|
||||
text={t`Group`}
|
||||
contextualText={
|
||||
isDefaultView
|
||||
? t`Not available on Default View`
|
||||
: recordGroupFieldMetadata?.label
|
||||
}
|
||||
hasSubMenu
|
||||
disabled={isDefaultView}
|
||||
/>
|
||||
</div>
|
||||
{!isGroupByEnabled && (
|
||||
<AppTooltip
|
||||
anchorSelect={`#group-by-menu-item`}
|
||||
content={t`Not available on Default View`}
|
||||
noArrow
|
||||
place="bottom"
|
||||
width="100%"
|
||||
/>
|
||||
)}
|
||||
<SelectableList
|
||||
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
|
||||
hotkeyScope={TableOptionsHotkeyScope.Dropdown}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
>
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<SelectableListItem
|
||||
itemId="Layout"
|
||||
onEnter={() => onContentChange('layout')}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === 'Layout'}
|
||||
onClick={() => onContentChange('layout')}
|
||||
LeftIcon={viewTypeIconMapping(
|
||||
currentView?.type ?? ViewType.Table,
|
||||
)}
|
||||
text={t`Layout`}
|
||||
contextualText={`${capitalize(currentView?.type ?? '')}`}
|
||||
hasSubMenu
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
const currentUrl = window.location.href;
|
||||
navigator.clipboard.writeText(currentUrl);
|
||||
enqueueSnackBar('Link copied to clipboard', {
|
||||
variant: SnackBarVariant.Success,
|
||||
icon: <IconCopy size={theme.icon.size.md} />,
|
||||
duration: 2000,
|
||||
});
|
||||
}}
|
||||
LeftIcon={IconCopy}
|
||||
text={t`Copy link to view`}
|
||||
/>
|
||||
<div id="delete-view-menu-item">
|
||||
<MenuItem
|
||||
onClick={() => handleDelete()}
|
||||
LeftIcon={IconTrash}
|
||||
text={t`Delete view`}
|
||||
disabled={currentView?.key === 'INDEX'}
|
||||
/>
|
||||
</div>
|
||||
{currentView?.key === 'INDEX' && (
|
||||
<AppTooltip
|
||||
anchorSelect={`#delete-view-menu-item`}
|
||||
content={t`Not available on Default View`}
|
||||
noArrow
|
||||
place="bottom"
|
||||
width="100%"
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<SelectableListItem
|
||||
itemId="Fields"
|
||||
onEnter={() => onContentChange('fields')}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === 'Fields'}
|
||||
onClick={() => onContentChange('fields')}
|
||||
LeftIcon={IconListDetails}
|
||||
text={t`Fields`}
|
||||
contextualText={`${visibleBoardFields.length} shown`}
|
||||
hasSubMenu
|
||||
/>
|
||||
</SelectableListItem>
|
||||
|
||||
<div id="group-by-menu-item">
|
||||
<SelectableListItem
|
||||
itemId="Group"
|
||||
onEnter={() =>
|
||||
isDefined(recordGroupFieldMetadata)
|
||||
? onContentChange('recordGroups')
|
||||
: onContentChange('recordGroupFields')
|
||||
}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === 'Group'}
|
||||
onClick={() =>
|
||||
isDefined(recordGroupFieldMetadata)
|
||||
? onContentChange('recordGroups')
|
||||
: onContentChange('recordGroupFields')
|
||||
}
|
||||
LeftIcon={IconLayoutList}
|
||||
text={t`Group`}
|
||||
contextualText={
|
||||
isDefaultView
|
||||
? t`Not available on Default View`
|
||||
: recordGroupFieldMetadata?.label
|
||||
}
|
||||
hasSubMenu
|
||||
disabled={isDefaultView}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</div>
|
||||
{!isGroupByEnabled && (
|
||||
<AppTooltip
|
||||
anchorSelect={`#group-by-menu-item`}
|
||||
content={t`Not available on Default View`}
|
||||
noArrow
|
||||
place="bottom"
|
||||
width="100%"
|
||||
/>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<SelectableListItem
|
||||
itemId="Copy link to view"
|
||||
onEnter={() => {
|
||||
const currentUrl = window.location.href;
|
||||
navigator.clipboard.writeText(currentUrl);
|
||||
enqueueSnackBar('Link copied to clipboard', {
|
||||
variant: SnackBarVariant.Success,
|
||||
icon: <IconCopy size={theme.icon.size.md} />,
|
||||
duration: 2000,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === 'Copy link to view'}
|
||||
onClick={() => {
|
||||
const currentUrl = window.location.href;
|
||||
navigator.clipboard.writeText(currentUrl);
|
||||
enqueueSnackBar('Link copied to clipboard', {
|
||||
variant: SnackBarVariant.Success,
|
||||
icon: <IconCopy size={theme.icon.size.md} />,
|
||||
duration: 2000,
|
||||
});
|
||||
}}
|
||||
LeftIcon={IconCopy}
|
||||
text={t`Copy link to view`}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
<div id="delete-view-menu-item">
|
||||
<SelectableListItem
|
||||
itemId="Delete view"
|
||||
onEnter={() => handleDelete()}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === 'Delete view'}
|
||||
onClick={() => handleDelete()}
|
||||
LeftIcon={IconTrash}
|
||||
text={t`Delete view`}
|
||||
disabled={currentView?.key === 'INDEX'}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</div>
|
||||
{currentView?.key === 'INDEX' && (
|
||||
<AppTooltip
|
||||
anchorSelect={`#delete-view-menu-item`}
|
||||
content={t`Not available on Default View`}
|
||||
noArrow
|
||||
place="bottom"
|
||||
width="100%"
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</SelectableList>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,14 +1,19 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId';
|
||||
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
|
||||
import { hiddenRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/hiddenRecordGroupIdsComponentSelector';
|
||||
import { RecordGroupSort } from '@/object-record/record-group/types/RecordGroupSort';
|
||||
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState';
|
||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import {
|
||||
IconChevronLeft,
|
||||
IconHandMove,
|
||||
@ -32,6 +37,11 @@ export const ObjectOptionsDropdownRecordGroupSortContent = () => {
|
||||
setRecordGroupSort(sort);
|
||||
};
|
||||
|
||||
const selectedItemId = useRecoilComponentValueV2(
|
||||
selectedItemIdComponentState,
|
||||
OBJECT_OPTIONS_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
currentContentId === 'hiddenRecordGroups' &&
|
||||
@ -41,6 +51,12 @@ export const ObjectOptionsDropdownRecordGroupSortContent = () => {
|
||||
}
|
||||
}, [hiddenRecordGroupIds, currentContentId, onContentChange]);
|
||||
|
||||
const selectableItemIdArray = [
|
||||
RecordGroupSort.Manual,
|
||||
RecordGroupSort.Alphabetical,
|
||||
RecordGroupSort.ReverseAlphabetical,
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader
|
||||
@ -54,28 +70,58 @@ export const ObjectOptionsDropdownRecordGroupSortContent = () => {
|
||||
Sort
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItemSelect
|
||||
onClick={() => handleRecordGroupSortChange(RecordGroupSort.Manual)}
|
||||
LeftIcon={IconHandMove}
|
||||
text={RecordGroupSort.Manual}
|
||||
selected={recordGroupSort === RecordGroupSort.Manual}
|
||||
/>
|
||||
<MenuItemSelect
|
||||
onClick={() =>
|
||||
handleRecordGroupSortChange(RecordGroupSort.Alphabetical)
|
||||
}
|
||||
LeftIcon={IconSortAZ}
|
||||
text={RecordGroupSort.Alphabetical}
|
||||
selected={recordGroupSort === RecordGroupSort.Alphabetical}
|
||||
/>
|
||||
<MenuItemSelect
|
||||
onClick={() =>
|
||||
handleRecordGroupSortChange(RecordGroupSort.ReverseAlphabetical)
|
||||
}
|
||||
LeftIcon={IconSortZA}
|
||||
text={RecordGroupSort.ReverseAlphabetical}
|
||||
selected={recordGroupSort === RecordGroupSort.ReverseAlphabetical}
|
||||
/>
|
||||
<SelectableList
|
||||
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
|
||||
hotkeyScope={TableOptionsHotkeyScope.Dropdown}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
>
|
||||
<SelectableListItem
|
||||
itemId={RecordGroupSort.Manual}
|
||||
onEnter={() => handleRecordGroupSortChange(RecordGroupSort.Manual)}
|
||||
>
|
||||
<MenuItemSelect
|
||||
onClick={() =>
|
||||
handleRecordGroupSortChange(RecordGroupSort.Manual)
|
||||
}
|
||||
LeftIcon={IconHandMove}
|
||||
text={RecordGroupSort.Manual}
|
||||
selected={recordGroupSort === RecordGroupSort.Manual}
|
||||
focused={selectedItemId === RecordGroupSort.Manual}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
<SelectableListItem
|
||||
itemId={RecordGroupSort.Alphabetical}
|
||||
onEnter={() =>
|
||||
handleRecordGroupSortChange(RecordGroupSort.Alphabetical)
|
||||
}
|
||||
>
|
||||
<MenuItemSelect
|
||||
onClick={() =>
|
||||
handleRecordGroupSortChange(RecordGroupSort.Alphabetical)
|
||||
}
|
||||
LeftIcon={IconSortAZ}
|
||||
text={RecordGroupSort.Alphabetical}
|
||||
selected={recordGroupSort === RecordGroupSort.Alphabetical}
|
||||
focused={selectedItemId === RecordGroupSort.Alphabetical}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
<SelectableListItem
|
||||
itemId={RecordGroupSort.ReverseAlphabetical}
|
||||
onEnter={() =>
|
||||
handleRecordGroupSortChange(RecordGroupSort.ReverseAlphabetical)
|
||||
}
|
||||
>
|
||||
<MenuItemSelect
|
||||
onClick={() =>
|
||||
handleRecordGroupSortChange(RecordGroupSort.ReverseAlphabetical)
|
||||
}
|
||||
LeftIcon={IconSortZA}
|
||||
text={RecordGroupSort.ReverseAlphabetical}
|
||||
selected={recordGroupSort === RecordGroupSort.ReverseAlphabetical}
|
||||
focused={selectedItemId === RecordGroupSort.ReverseAlphabetical}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId';
|
||||
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
|
||||
import { RecordGroupReorderConfirmationModal } from '@/object-record/record-group/components/RecordGroupReorderConfirmationModal';
|
||||
import { RecordGroupsVisibilityDropdownSection } from '@/object-record/record-group/components/RecordGroupsVisibilityDropdownSection';
|
||||
@ -10,10 +11,14 @@ import { hiddenRecordGroupIdsComponentSelector } from '@/object-record/record-gr
|
||||
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
|
||||
import { recordIndexRecordGroupHideComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentFamilyState';
|
||||
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState';
|
||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
@ -89,6 +94,17 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
|
||||
}
|
||||
}, [hiddenRecordGroupIds, currentContentId, onContentChange]);
|
||||
|
||||
const selectedItemId = useRecoilComponentValueV2(
|
||||
selectedItemIdComponentState,
|
||||
OBJECT_OPTIONS_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
const selectableItemIdArray = [
|
||||
...(currentView?.key !== 'INDEX' ? ['GroupBy', 'Sort'] : []),
|
||||
'HideEmptyGroups',
|
||||
...(hiddenRecordGroupIds.length > 0 ? ['HiddenGroups'] : []),
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader
|
||||
@ -102,31 +118,55 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
|
||||
Group
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuItemsContainer>
|
||||
{currentView?.key !== 'INDEX' && (
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={() => onContentChange('recordGroupFields')}
|
||||
LeftIcon={IconLayoutList}
|
||||
text={t`Group by`}
|
||||
contextualText={recordGroupFieldMetadata?.label}
|
||||
hasSubMenu
|
||||
<SelectableList
|
||||
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
|
||||
hotkeyScope={TableOptionsHotkeyScope.Dropdown}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
>
|
||||
{currentView?.key !== 'INDEX' && (
|
||||
<>
|
||||
<SelectableListItem
|
||||
itemId="GroupBy"
|
||||
onEnter={() => onContentChange('recordGroupFields')}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === 'GroupBy'}
|
||||
onClick={() => onContentChange('recordGroupFields')}
|
||||
LeftIcon={IconLayoutList}
|
||||
text={t`Group by`}
|
||||
contextualText={recordGroupFieldMetadata?.label}
|
||||
hasSubMenu
|
||||
/>
|
||||
</SelectableListItem>
|
||||
<SelectableListItem
|
||||
itemId="Sort"
|
||||
onEnter={() => onContentChange('recordGroupSort')}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === 'Sort'}
|
||||
onClick={() => onContentChange('recordGroupSort')}
|
||||
LeftIcon={IconSortDescending}
|
||||
text={t`Sort`}
|
||||
contextualText={recordGroupSort}
|
||||
hasSubMenu
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</>
|
||||
)}
|
||||
<SelectableListItem
|
||||
itemId="HideEmptyGroups"
|
||||
onEnter={() => handleHideEmptyRecordGroupChange()}
|
||||
>
|
||||
<MenuItemToggle
|
||||
focused={selectedItemId === 'HideEmptyGroups'}
|
||||
LeftIcon={IconCircleOff}
|
||||
onToggleChange={handleHideEmptyRecordGroupChange}
|
||||
toggled={hideEmptyRecordGroup}
|
||||
text={t`Hide empty groups`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
<MenuItem
|
||||
onClick={() => onContentChange('recordGroupSort')}
|
||||
LeftIcon={IconSortDescending}
|
||||
text={t`Sort`}
|
||||
contextualText={recordGroupSort}
|
||||
hasSubMenu
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconCircleOff}
|
||||
onToggleChange={handleHideEmptyRecordGroupChange}
|
||||
toggled={hideEmptyRecordGroup}
|
||||
text={t`Hide empty groups`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
{visibleRecordGroupIds.length > 0 && (
|
||||
<>
|
||||
@ -145,11 +185,16 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<MenuItemNavigate
|
||||
onClick={() => onContentChange('hiddenRecordGroups')}
|
||||
LeftIcon={IconEyeOff}
|
||||
text={`Hidden ${recordGroupFieldMetadata?.label ?? ''}`}
|
||||
/>
|
||||
<SelectableListItem
|
||||
itemId="HiddenGroups"
|
||||
onEnter={() => onContentChange('hiddenRecordGroups')}
|
||||
>
|
||||
<MenuItemNavigate
|
||||
onClick={() => onContentChange('hiddenRecordGroups')}
|
||||
LeftIcon={IconEyeOff}
|
||||
text={`Hidden ${recordGroupFieldMetadata?.label ?? ''}`}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -24,16 +24,19 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { v4 } from 'uuid';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { IconChevronDown, useIcons } from 'twenty-ui/display';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export const StyledInput = styled.input`
|
||||
background: transparent;
|
||||
@ -191,6 +194,21 @@ export const ObjectSortDropdownButton = ({
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const selectableItemIdArray = [
|
||||
...visibleFieldMetadataItems.map((item) => item.id),
|
||||
...hiddenFieldMetadataItems.map((item) => item.id),
|
||||
];
|
||||
|
||||
const selectedItemId = useRecoilComponentValueV2(
|
||||
selectedItemIdComponentState,
|
||||
OBJECT_SORT_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
const setSelectedItemId = useSetRecoilComponentStateV2(
|
||||
selectedItemIdComponentState,
|
||||
OBJECT_SORT_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
dropdownId={OBJECT_SORT_DROPDOWN_ID}
|
||||
@ -198,20 +216,28 @@ export const ObjectSortDropdownButton = ({
|
||||
dropdownOffset={{ y: 8 }}
|
||||
clickableComponent={
|
||||
<StyledHeaderDropdownButton
|
||||
onClick={handleButtonClick}
|
||||
onClick={() => {
|
||||
handleButtonClick();
|
||||
setSelectedItemId(selectableItemIdArray[0]);
|
||||
}}
|
||||
isUnfolded={isDropdownOpen}
|
||||
>
|
||||
<Trans>Sort</Trans>
|
||||
</StyledHeaderDropdownButton>
|
||||
}
|
||||
dropdownComponents={
|
||||
<>
|
||||
<SelectableList
|
||||
selectableListInstanceId={OBJECT_SORT_DROPDOWN_ID}
|
||||
hotkeyScope={hotkeyScope.scope}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
>
|
||||
{isRecordSortDirectionMenuUnfolded && (
|
||||
<StyledSelectedSortDirectionContainer>
|
||||
<DropdownMenuItemsContainer>
|
||||
{RECORD_SORT_DIRECTIONS.map((sortDirection, index) => (
|
||||
<MenuItem
|
||||
key={index}
|
||||
focused={selectedItemId === sortDirection}
|
||||
onClick={() => handleSortDirectionClick(sortDirection)}
|
||||
text={
|
||||
sortDirection === 'asc' ? t`Ascending` : t`Descending`
|
||||
@ -244,27 +270,39 @@ export const ObjectSortDropdownButton = ({
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
{visibleFieldMetadataItems.map(
|
||||
(visibleFieldMetadataItem, index) => (
|
||||
<MenuItem
|
||||
testId={`visible-select-sort-${index}`}
|
||||
key={index}
|
||||
onClick={() => handleAddSort(visibleFieldMetadataItem)}
|
||||
LeftIcon={getIcon(visibleFieldMetadataItem.icon)}
|
||||
text={visibleFieldMetadataItem.label}
|
||||
/>
|
||||
<SelectableListItem
|
||||
key={visibleFieldMetadataItem.id}
|
||||
itemId={visibleFieldMetadataItem.id}
|
||||
onEnter={() => handleAddSort(visibleFieldMetadataItem)}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === visibleFieldMetadataItem.id}
|
||||
testId={`visible-select-sort-${index}`}
|
||||
onClick={() => handleAddSort(visibleFieldMetadataItem)}
|
||||
LeftIcon={getIcon(visibleFieldMetadataItem.icon)}
|
||||
text={visibleFieldMetadataItem.label}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
),
|
||||
)}
|
||||
{shouldShowSeparator && <DropdownMenuSeparator />}
|
||||
{hiddenFieldMetadataItems.map((hiddenFieldMetadataItem, index) => (
|
||||
<MenuItem
|
||||
testId={`hidden-select-sort-${index}`}
|
||||
key={index}
|
||||
onClick={() => handleAddSort(hiddenFieldMetadataItem)}
|
||||
LeftIcon={getIcon(hiddenFieldMetadataItem.icon)}
|
||||
text={hiddenFieldMetadataItem.label}
|
||||
/>
|
||||
<SelectableListItem
|
||||
key={hiddenFieldMetadataItem.id}
|
||||
itemId={hiddenFieldMetadataItem.id}
|
||||
onEnter={() => handleAddSort(hiddenFieldMetadataItem)}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === hiddenFieldMetadataItem.id}
|
||||
testId={`hidden-select-sort-${index}`}
|
||||
onClick={() => handleAddSort(hiddenFieldMetadataItem)}
|
||||
LeftIcon={getIcon(hiddenFieldMetadataItem.icon)}
|
||||
text={hiddenFieldMetadataItem.label}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
</SelectableList>
|
||||
}
|
||||
onClose={handleDropdownButtonClose}
|
||||
/>
|
||||
|
||||
@ -4,9 +4,8 @@ import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/get
|
||||
import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId';
|
||||
import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardSelectedComponentFamilyState';
|
||||
import { recordBoardSelectedRecordIdsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardSelectedRecordIdsComponentSelector';
|
||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
|
||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
|
||||
export const useRecordBoardSelection = (recordBoardId: string) => {
|
||||
@ -22,17 +21,16 @@ export const useRecordBoardSelection = (recordBoardId: string) => {
|
||||
recordBoardId,
|
||||
);
|
||||
|
||||
const isActionMenuDropdownOpenState = extractComponentState(
|
||||
isDropdownOpenComponentState,
|
||||
getActionMenuDropdownIdFromActionMenuId(
|
||||
getActionMenuIdFromRecordIndexId(recordBoardId),
|
||||
),
|
||||
const { closeDropdown } = useDropdownV2();
|
||||
|
||||
const dropdownId = getActionMenuDropdownIdFromActionMenuId(
|
||||
getActionMenuIdFromRecordIndexId(recordBoardId),
|
||||
);
|
||||
|
||||
const resetRecordSelection = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
() => {
|
||||
set(isActionMenuDropdownOpenState, false);
|
||||
closeDropdown(dropdownId);
|
||||
|
||||
const recordIds = getSnapshotValue(
|
||||
snapshot,
|
||||
@ -44,7 +42,8 @@ export const useRecordBoardSelection = (recordBoardId: string) => {
|
||||
}
|
||||
},
|
||||
[
|
||||
isActionMenuDropdownOpenState,
|
||||
closeDropdown,
|
||||
dropdownId,
|
||||
recordBoardSelectedRecordIdsSelector,
|
||||
isRecordBoardCardSelectedFamilyState,
|
||||
],
|
||||
@ -67,17 +66,17 @@ export const useRecordBoardSelection = (recordBoardId: string) => {
|
||||
);
|
||||
|
||||
const checkIfLastUnselectAndCloseDropdown = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
({ snapshot }) =>
|
||||
() => {
|
||||
const recordIds = getSnapshotValue(
|
||||
snapshot,
|
||||
recordBoardSelectedRecordIdsSelector,
|
||||
);
|
||||
if (recordIds.length === 0) {
|
||||
set(isActionMenuDropdownOpenState, false);
|
||||
closeDropdown(dropdownId);
|
||||
}
|
||||
},
|
||||
[recordBoardSelectedRecordIdsSelector, isActionMenuDropdownOpenState],
|
||||
[recordBoardSelectedRecordIdsSelector, closeDropdown, dropdownId],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@ -7,6 +7,7 @@ import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/r
|
||||
import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState';
|
||||
import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector';
|
||||
|
||||
import { ActionMenuDropdownHotkeyScope } from '@/action-menu/types/ActionMenuDropdownHotKeyScope';
|
||||
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
|
||||
import { RecordBoardCardBody } from '@/object-record/record-board/record-board-card/components/RecordBoardCardBody';
|
||||
import { RecordBoardCardHeader } from '@/object-record/record-board/record-board-card/components/RecordBoardCardHeader';
|
||||
@ -121,7 +122,9 @@ export const RecordBoardCard = () => {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
});
|
||||
openDropdown(actionMenuDropdownId);
|
||||
openDropdown(actionMenuDropdownId, {
|
||||
scope: ActionMenuDropdownHotkeyScope.ActionMenuDropdown,
|
||||
});
|
||||
};
|
||||
|
||||
const handleCardClick = () => {
|
||||
|
||||
@ -15,7 +15,7 @@ import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
@ -28,7 +28,7 @@ import { Key } from 'ts-key-enum';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
|
||||
export const StyledSelectableItem = styled(SelectableItem)`
|
||||
export const StyledSelectableItem = styled(SelectableListItem)`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
@ -3,10 +3,10 @@ import styled from '@emotion/styled';
|
||||
import { useRecordPickerGetSearchRecordAndObjectMetadataItemFromRecordId } from '@/object-record/record-picker/hooks/useRecordPickerGetSearchRecordAndObjectMetadataItemFromRecordId';
|
||||
import { MultipleRecordPickerMenuItemContent } from '@/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItemContent';
|
||||
import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
|
||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const StyledSelectableItem = styled(SelectableItem)`
|
||||
export const StyledSelectableItem = styled(SelectableListItem)`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
@ -6,7 +6,7 @@ import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/re
|
||||
import { multipleRecordPickerIsSelectedComponentFamilySelector } from '@/object-record/record-picker/multiple-record-picker/states/selectors/multipleRecordPickerIsSelectedComponentFamilySelector';
|
||||
import { getMultipleRecordPickerSelectableListId } from '@/object-record/record-picker/multiple-record-picker/utils/getMultipleRecordPickerSelectableListId';
|
||||
import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
|
||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
@ -14,7 +14,7 @@ import { Avatar } from 'twenty-ui/display';
|
||||
import { MenuItemMultiSelectAvatar } from 'twenty-ui/navigation';
|
||||
import { SearchRecord } from '~/generated-metadata/graphql';
|
||||
|
||||
export const StyledSelectableItem = styled(SelectableItem)`
|
||||
export const StyledSelectableItem = styled(SelectableListItem)`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`;
|
||||
@ -62,6 +62,7 @@ export const MultipleRecordPickerMenuItemContent = ({
|
||||
<StyledSelectableItem
|
||||
itemId={searchRecord.recordId}
|
||||
key={searchRecord.recordId}
|
||||
onEnter={() => handleSelectChange(!isRecordSelectedWithObjectItem)}
|
||||
>
|
||||
<MenuItemMultiSelectAvatar
|
||||
onSelectChange={(isSelected) => handleSelectChange(isSelected)}
|
||||
|
||||
@ -4,21 +4,19 @@ import { MultipleRecordPickerMenuItem } from '@/object-record/record-picker/mult
|
||||
import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/multiple-record-picker/states/contexts/MultipleRecordPickerComponentInstanceContext';
|
||||
import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState';
|
||||
import { multipleRecordPickerPickableRecordIdsMatchingSearchComponentSelector } from '@/object-record/record-picker/multiple-record-picker/states/selectors/multipleRecordPickerPickableRecordIdsMatchingSearchComponentSelector';
|
||||
import { multipleRecordPickerSinglePickableMorphItemComponentFamilySelector } from '@/object-record/record-picker/multiple-record-picker/states/selectors/multipleRecordPickerSinglePickableMorphItemComponentFamilySelector';
|
||||
import { MultipleRecordPickerHotkeyScope } from '@/object-record/record-picker/multiple-record-picker/types/MultipleRecordPickerHotkeyScope';
|
||||
import { getMultipleRecordPickerSelectableListId } from '@/object-record/record-picker/multiple-record-picker/utils/getMultipleRecordPickerSelectableListId';
|
||||
import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const StyledSelectableItem = styled(SelectableItem)`
|
||||
export const StyledSelectableItem = styled(SelectableListItem)`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`;
|
||||
@ -45,11 +43,6 @@ export const MultipleRecordPickerMenuItems = ({
|
||||
const { resetSelectedItem } = useSelectableList(
|
||||
selectableListComponentInstanceId,
|
||||
);
|
||||
const singlePickableMorphItemFamilySelector =
|
||||
useRecoilComponentCallbackStateV2(
|
||||
multipleRecordPickerSinglePickableMorphItemComponentFamilySelector,
|
||||
componentInstanceId,
|
||||
);
|
||||
|
||||
const multipleRecordPickerPickableMorphItemsState =
|
||||
useRecoilComponentCallbackStateV2(
|
||||
@ -82,42 +75,12 @@ export const MultipleRecordPickerMenuItems = ({
|
||||
[multipleRecordPickerPickableMorphItemsState],
|
||||
);
|
||||
|
||||
const handleEnter = useRecoilCallback(
|
||||
({ snapshot }) => {
|
||||
return (selectedId: string) => {
|
||||
const pickableMorphItem = snapshot
|
||||
.getLoadable(singlePickableMorphItemFamilySelector(selectedId))
|
||||
.getValue();
|
||||
|
||||
if (!isDefined(pickableMorphItem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedMorphItem = {
|
||||
...pickableMorphItem,
|
||||
isSelected: !pickableMorphItem.isSelected,
|
||||
};
|
||||
|
||||
handleChange(selectedMorphItem);
|
||||
onChange?.(selectedMorphItem);
|
||||
resetSelectedItem();
|
||||
};
|
||||
},
|
||||
[
|
||||
handleChange,
|
||||
onChange,
|
||||
resetSelectedItem,
|
||||
singlePickableMorphItemFamilySelector,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
<SelectableList
|
||||
selectableListInstanceId={selectableListComponentInstanceId}
|
||||
selectableItemIdArray={pickableRecordIds}
|
||||
hotkeyScope={MultipleRecordPickerHotkeyScope.MultipleRecordPicker}
|
||||
onEnter={handleEnter}
|
||||
>
|
||||
{pickableRecordIds.map((recordId) => {
|
||||
return (
|
||||
|
||||
@ -1,19 +1,11 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useMultipleRecordPickerPerformSearch } from '@/object-record/record-picker/multiple-record-picker/hooks/useMultipleRecordPickerPerformSearch';
|
||||
import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/multiple-record-picker/states/contexts/MultipleRecordPickerComponentInstanceContext';
|
||||
import { multipleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchFilterComponentState';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const StyledSelectableItem = styled(SelectableItem)`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const MultipleRecordPickerSearchInput = () => {
|
||||
const componentInstanceId = useAvailableComponentInstanceIdOrThrow(
|
||||
MultipleRecordPickerComponentInstanceContext,
|
||||
|
||||
@ -3,7 +3,7 @@ import styled from '@emotion/styled';
|
||||
import { SingleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/single-record-picker/states/contexts/SingleRecordPickerComponentInstanceContext';
|
||||
import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord';
|
||||
import { getSingleRecordPickerSelectableListId } from '@/object-record/record-picker/single-record-picker/utils/getSingleRecordPickerSelectableListId';
|
||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
@ -16,7 +16,7 @@ type SingleRecordPickerMenuItemProps = {
|
||||
selectedRecord?: SingleRecordPickerRecord;
|
||||
};
|
||||
|
||||
const StyledSelectableItem = styled(SelectableItem)`
|
||||
const StyledSelectableItem = styled(SelectableListItem)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
@ -40,14 +40,19 @@ export const SingleRecordPickerMenuItem = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledSelectableItem itemId={record.id} key={record.id}>
|
||||
<StyledSelectableItem
|
||||
itemId={record.id}
|
||||
key={record.id}
|
||||
onEnter={() => {
|
||||
onRecordSelected(record);
|
||||
}}
|
||||
>
|
||||
<MenuItemSelectAvatar
|
||||
key={record.id}
|
||||
testId="menu-item"
|
||||
onClick={() => onRecordSelected(record)}
|
||||
text={record.name}
|
||||
selected={selectedRecord?.id === record.id}
|
||||
hovered={isSelectedItemId}
|
||||
focused={isSelectedItemId}
|
||||
avatar={
|
||||
<Avatar
|
||||
avatarUrl={record.avatarUrl}
|
||||
|
||||
@ -14,6 +14,7 @@ import { singleRecordPickerSelectedIdComponentState } from '@/object-record/reco
|
||||
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
|
||||
import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord';
|
||||
import { getSingleRecordPickerSelectableListId } from '@/object-record/record-picker/single-record-picker/utils/getSingleRecordPickerSelectableListId';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
@ -108,14 +109,6 @@ export const SingleRecordPickerMenuItems = ({
|
||||
selectableListInstanceId={selectableListComponentInstanceId}
|
||||
selectableItemIdArray={selectableItemIds}
|
||||
hotkeyScope={hotkeyScope}
|
||||
onEnter={(itemId) => {
|
||||
const recordIndex = recordsInDropdown.findIndex(
|
||||
(record) => record.id === itemId,
|
||||
);
|
||||
setSelectedRecordId(itemId);
|
||||
onRecordSelected(recordsInDropdown[recordIndex]);
|
||||
resetSelectedItem();
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{loading && !isFiltered ? (
|
||||
@ -128,17 +121,25 @@ export const SingleRecordPickerMenuItems = ({
|
||||
case 'select-none': {
|
||||
return (
|
||||
emptyLabel && (
|
||||
<MenuItemSelect
|
||||
<SelectableListItem
|
||||
key={record.id}
|
||||
onClick={() => {
|
||||
itemId={record.id}
|
||||
onEnter={() => {
|
||||
setSelectedRecordId(undefined);
|
||||
onRecordSelected();
|
||||
}}
|
||||
LeftIcon={EmptyIcon}
|
||||
text={emptyLabel}
|
||||
selected={isUndefined(selectedRecordId)}
|
||||
hovered={isSelectedSelectNoneButton}
|
||||
/>
|
||||
>
|
||||
<MenuItemSelect
|
||||
onClick={() => {
|
||||
setSelectedRecordId(undefined);
|
||||
onRecordSelected();
|
||||
}}
|
||||
LeftIcon={EmptyIcon}
|
||||
text={emptyLabel}
|
||||
selected={isUndefined(selectedRecordId)}
|
||||
focused={isSelectedSelectNoneButton}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,15 +2,15 @@ import { useRecoilCallback } from 'recoil';
|
||||
|
||||
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 { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
|
||||
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
|
||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
||||
|
||||
export const useTriggerActionMenuDropdown = ({
|
||||
recordTableId,
|
||||
}: {
|
||||
@ -25,18 +25,17 @@ export const useTriggerActionMenuDropdown = ({
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const actionMenuDropdownId =
|
||||
getActionMenuDropdownIdFromActionMenuId(actionMenuInstanceId);
|
||||
|
||||
const recordIndexActionMenuDropdownPositionState = extractComponentState(
|
||||
recordIndexActionMenuDropdownPositionComponentState,
|
||||
getActionMenuDropdownIdFromActionMenuId(actionMenuInstanceId),
|
||||
actionMenuDropdownId,
|
||||
);
|
||||
|
||||
const isActionMenuDropdownOpenState = extractComponentState(
|
||||
isDropdownOpenComponentState,
|
||||
getActionMenuDropdownIdFromActionMenuId(actionMenuInstanceId),
|
||||
);
|
||||
const { openDropdown } = useDropdown(actionMenuDropdownId);
|
||||
|
||||
const { setActiveDropdownFocusIdAndMemorizePrevious } =
|
||||
useSetActiveDropdownFocusIdAndMemorizePrevious();
|
||||
const { closeCommandMenu } = useCommandMenu();
|
||||
|
||||
const triggerActionMenuDropdown = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
@ -57,19 +56,17 @@ export const useTriggerActionMenuDropdown = ({
|
||||
set(isRowSelectedFamilyState(recordId), true);
|
||||
}
|
||||
|
||||
set(isActionMenuDropdownOpenState, true);
|
||||
closeCommandMenu();
|
||||
|
||||
const actionMenuDropdownId =
|
||||
getActionMenuDropdownIdFromActionMenuId(actionMenuInstanceId);
|
||||
|
||||
setActiveDropdownFocusIdAndMemorizePrevious(actionMenuDropdownId);
|
||||
openDropdown({
|
||||
scope: ActionMenuDropdownHotkeyScope.ActionMenuDropdown,
|
||||
});
|
||||
},
|
||||
[
|
||||
isActionMenuDropdownOpenState,
|
||||
isRowSelectedFamilyState,
|
||||
recordIndexActionMenuDropdownPositionState,
|
||||
setActiveDropdownFocusIdAndMemorizePrevious,
|
||||
actionMenuInstanceId,
|
||||
isRowSelectedFamilyState,
|
||||
closeCommandMenu,
|
||||
openDropdown,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
@ -93,43 +94,38 @@ export const MultipleSelectDropdown = ({
|
||||
selectableListInstanceId={selectableListId}
|
||||
selectableItemIdArray={selectableItemIds}
|
||||
hotkeyScope={hotkeyScope}
|
||||
onEnter={(itemId) => {
|
||||
const item = itemsInDropdown.findIndex(
|
||||
(entity) => entity.id === itemId,
|
||||
);
|
||||
const itemIsSelectedInDropwdown = filteredSelectedItems.find(
|
||||
(entity) => entity.id === itemId,
|
||||
);
|
||||
handleItemSelectChange(
|
||||
itemsInDropdown[item],
|
||||
!itemIsSelectedInDropwdown,
|
||||
);
|
||||
resetSelectedItem();
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{itemsInDropdown?.map((item) => {
|
||||
return (
|
||||
<MenuItemMultiSelectAvatar
|
||||
key={item.id}
|
||||
selected={item.isSelected}
|
||||
isKeySelected={item.id === selectedItemId}
|
||||
onSelectChange={(newCheckedValue) => {
|
||||
<SelectableListItem
|
||||
itemId={item.id}
|
||||
onEnter={() => {
|
||||
resetSelectedItem();
|
||||
handleItemSelectChange(item, newCheckedValue);
|
||||
handleItemSelectChange(item, !item.isSelected);
|
||||
}}
|
||||
avatar={
|
||||
<StyledMultipleSelectDropdownAvatarChip
|
||||
className="avatar-icon-container"
|
||||
name={item.name}
|
||||
avatarUrl={item.avatarUrl}
|
||||
LeftIcon={item.AvatarIcon}
|
||||
avatarType={item.avatarType}
|
||||
isIconInverted={item.isIconInverted}
|
||||
placeholderColorSeed={item.id}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
>
|
||||
<MenuItemMultiSelectAvatar
|
||||
key={item.id}
|
||||
selected={item.isSelected}
|
||||
isKeySelected={item.id === selectedItemId}
|
||||
onSelectChange={(newCheckedValue) => {
|
||||
resetSelectedItem();
|
||||
handleItemSelectChange(item, newCheckedValue);
|
||||
}}
|
||||
avatar={
|
||||
<StyledMultipleSelectDropdownAvatarChip
|
||||
className="avatar-icon-container"
|
||||
name={item.name}
|
||||
avatarUrl={item.avatarUrl}
|
||||
LeftIcon={item.AvatarIcon}
|
||||
avatarType={item.avatarType}
|
||||
isIconInverted={item.isIconInverted}
|
||||
placeholderColorSeed={item.id}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
);
|
||||
})}
|
||||
{showNoResult && <MenuItem text="No results" />}
|
||||
|
||||
@ -101,14 +101,6 @@ export const MultiSelectInput = ({
|
||||
selectableListInstanceId={selectableListComponentInstanceId}
|
||||
selectableItemIdArray={optionIds}
|
||||
hotkeyScope={hotkeyScope}
|
||||
onEnter={(itemId) => {
|
||||
const option = filteredOptionsInDropDown.find(
|
||||
(option) => option.value === itemId,
|
||||
);
|
||||
if (isDefined(option)) {
|
||||
onOptionSelected(formatNewSelectedOptions(option.value));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DropdownMenu data-select-disable ref={containerRef}>
|
||||
<DropdownMenuSearchInput
|
||||
|
||||
@ -20,7 +20,6 @@ export const SelectInput = ({
|
||||
selectableListComponentInstanceId,
|
||||
selectableItemIdArray,
|
||||
hotkeyScope,
|
||||
onEnter,
|
||||
onOptionSelected,
|
||||
options,
|
||||
onCancel,
|
||||
@ -34,7 +33,6 @@ export const SelectInput = ({
|
||||
selectableListInstanceId={selectableListComponentInstanceId}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
hotkeyScope={hotkeyScope}
|
||||
onEnter={onEnter}
|
||||
>
|
||||
<SelectBaseInput
|
||||
onOptionSelected={onOptionSelected}
|
||||
|
||||
@ -11,6 +11,7 @@ import { SelectableList } from '@/ui/layout/selectable-list/components/Selectabl
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { arrayToChunks } from '~/utils/array/arrayToChunks';
|
||||
|
||||
import { useSelectableListListenToEnterHotkeyOnItem } from '@/ui/layout/selectable-list/hooks/useSelectableListListenToEnterHotkeyOnItem';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { t } from '@lingui/core/macro';
|
||||
@ -22,7 +23,6 @@ import {
|
||||
LightIconButton,
|
||||
} from 'twenty-ui/input';
|
||||
import { IconPickerHotkeyScope } from '../types/IconPickerHotkeyScope';
|
||||
|
||||
export type IconPickerProps = {
|
||||
disabled?: boolean;
|
||||
dropdownId?: string;
|
||||
@ -69,6 +69,12 @@ const IconPickerIcon = ({
|
||||
iconKey,
|
||||
);
|
||||
|
||||
useSelectableListListenToEnterHotkeyOnItem({
|
||||
hotkeyScope: IconPickerHotkeyScope.IconPicker,
|
||||
itemId: iconKey,
|
||||
onEnter: onClick,
|
||||
});
|
||||
|
||||
return (
|
||||
<StyledLightIconButton
|
||||
key={iconKey}
|
||||
@ -179,10 +185,6 @@ export const IconPicker = ({
|
||||
selectableListInstanceId="icon-list"
|
||||
selectableItemIdMatrix={iconKeys2d}
|
||||
hotkeyScope={IconPickerHotkeyScope.IconPicker}
|
||||
onEnter={(iconKey) => {
|
||||
onChange({ iconKey, Icon: getIcon(iconKey) });
|
||||
closeDropdown();
|
||||
}}
|
||||
>
|
||||
<DropdownMenu width={176}>
|
||||
<DropdownMenuSearchInput
|
||||
|
||||
@ -9,6 +9,10 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
|
||||
import { SelectControl } from '@/ui/input/components/SelectControl';
|
||||
import { DropdownOffset } from '@/ui/layout/dropdown/types/DropdownOffset';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconComponent } from 'twenty-ui/display';
|
||||
import { SelectOption } from 'twenty-ui/input';
|
||||
@ -109,6 +113,13 @@ export const Select = <Value extends SelectValue>({
|
||||
? selectContainerRef.current?.clientWidth
|
||||
: dropdownWidth;
|
||||
|
||||
const selectableItemIdArray = filteredOptions.map((option) => option.label);
|
||||
|
||||
const selectedItemId = useRecoilComponentValueV2(
|
||||
selectedItemIdComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledContainer
|
||||
className={className}
|
||||
@ -153,20 +164,36 @@ export const Select = <Value extends SelectValue>({
|
||||
)}
|
||||
{!!filteredOptions.length && (
|
||||
<DropdownMenuItemsContainer hasMaxHeight width={'auto'}>
|
||||
{filteredOptions.map((option) => (
|
||||
<MenuItemSelect
|
||||
key={`${option.value}-${option.label}`}
|
||||
LeftIcon={option.Icon}
|
||||
text={option.label}
|
||||
selected={selectedOption.value === option.value}
|
||||
needIconCheck={needIconCheck}
|
||||
onClick={() => {
|
||||
onChange?.(option.value);
|
||||
onBlur?.();
|
||||
closeDropdown();
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<SelectableList
|
||||
hotkeyScope={SelectHotkeyScope.Select}
|
||||
selectableListInstanceId={dropdownId}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
>
|
||||
{filteredOptions.map((option) => (
|
||||
<SelectableListItem
|
||||
key={`${option.value}-${option.label}`}
|
||||
itemId={option.label}
|
||||
onEnter={() => {
|
||||
onChange?.(option.value);
|
||||
onBlur?.();
|
||||
closeDropdown();
|
||||
}}
|
||||
>
|
||||
<MenuItemSelect
|
||||
LeftIcon={option.Icon}
|
||||
text={option.label}
|
||||
selected={selectedOption.value === option.value}
|
||||
focused={selectedItemId === option.label}
|
||||
needIconCheck={needIconCheck}
|
||||
onClick={() => {
|
||||
onChange?.(option.value);
|
||||
onBlur?.();
|
||||
closeDropdown();
|
||||
}}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
))}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
)}
|
||||
{!!callToActionButton && !!filteredOptions.length && (
|
||||
|
||||
@ -7,9 +7,9 @@ import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useLis
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { MenuItemSelectTag } from 'twenty-ui/navigation';
|
||||
import { SelectOption } from 'twenty-ui/input';
|
||||
import { TagColor } from 'twenty-ui/components';
|
||||
import { SelectOption } from 'twenty-ui/input';
|
||||
import { MenuItemSelectTag } from 'twenty-ui/navigation';
|
||||
|
||||
interface SelectInputProps {
|
||||
onOptionSelected: (selectedOption: SelectOption) => void;
|
||||
@ -107,7 +107,6 @@ export const SelectInput = ({
|
||||
{onClear && clearLabel && (
|
||||
<MenuItemSelectTag
|
||||
key={`No ${clearLabel}`}
|
||||
selected={false}
|
||||
text={`No ${clearLabel}`}
|
||||
color="transparent"
|
||||
variant={'outline'}
|
||||
@ -121,7 +120,7 @@ export const SelectInput = ({
|
||||
return (
|
||||
<MenuItemSelectTag
|
||||
key={option.value}
|
||||
selected={selectedOption?.value === option.value}
|
||||
focused={selectedOption?.value === option.value}
|
||||
text={option.label}
|
||||
color={(option.color as TagColor) ?? 'transparent'}
|
||||
onClick={() => handleOptionChange(option)}
|
||||
|
||||
@ -2,8 +2,8 @@ import { ReactNode, useEffect } from 'react';
|
||||
|
||||
import { useSelectableListHotKeys } from '@/ui/layout/selectable-list/hooks/internal/useSelectableListHotKeys';
|
||||
import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
|
||||
import { SelectableListContextProvider } from '@/ui/layout/selectable-list/states/contexts/SelectableListContext';
|
||||
import { selectableItemIdsComponentState } from '@/ui/layout/selectable-list/states/selectableItemIdsComponentState';
|
||||
import { selectableListOnEnterComponentState } from '@/ui/layout/selectable-list/states/selectableListOnEnterComponentState';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { arrayToChunks } from '~/utils/array/arrayToChunks';
|
||||
@ -14,7 +14,6 @@ type SelectableListProps = {
|
||||
selectableItemIdMatrix?: string[][];
|
||||
onSelect?: (selected: string) => void;
|
||||
hotkeyScope: string;
|
||||
onEnter?: (itemId: string) => void;
|
||||
selectableListInstanceId: string;
|
||||
};
|
||||
|
||||
@ -24,25 +23,15 @@ export const SelectableList = ({
|
||||
selectableItemIdArray,
|
||||
selectableItemIdMatrix,
|
||||
selectableListInstanceId,
|
||||
onEnter,
|
||||
onSelect,
|
||||
}: SelectableListProps) => {
|
||||
useSelectableListHotKeys(selectableListInstanceId, hotkeyScope, onSelect);
|
||||
|
||||
const setSelectableListOnEnter = useSetRecoilComponentStateV2(
|
||||
selectableListOnEnterComponentState,
|
||||
selectableListInstanceId,
|
||||
);
|
||||
|
||||
const setSelectableItemIds = useSetRecoilComponentStateV2(
|
||||
selectableItemIdsComponentState,
|
||||
selectableListInstanceId,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectableListOnEnter(() => onEnter);
|
||||
}, [onEnter, setSelectableListOnEnter]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectableItemIdArray && !selectableItemIdMatrix) {
|
||||
throw new Error(
|
||||
@ -65,7 +54,9 @@ export const SelectableList = ({
|
||||
instanceId: selectableListInstanceId,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<SelectableListContextProvider value={{ hotkeyScope }}>
|
||||
{children}
|
||||
</SelectableListContextProvider>
|
||||
</SelectableListComponentInstanceContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,25 +1,29 @@
|
||||
import { ReactNode, useEffect, useRef } from 'react';
|
||||
|
||||
import { SelectableListItemHotkeyEffect } from '@/ui/layout/selectable-list/components/SelectableListItemHotkeyEffect';
|
||||
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import styled from '@emotion/styled';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export type SelectableItemProps = {
|
||||
export type SelectableListItemProps = {
|
||||
itemId: string;
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
onEnter?: () => void;
|
||||
};
|
||||
|
||||
export const SelectableItem = ({
|
||||
export const SelectableListItem = ({
|
||||
itemId,
|
||||
children,
|
||||
className,
|
||||
}: SelectableItemProps) => {
|
||||
onEnter,
|
||||
}: SelectableListItemProps) => {
|
||||
const isSelectedItemId = useRecoilComponentFamilyValueV2(
|
||||
isSelectedItemIdComponentFamilySelector,
|
||||
itemId,
|
||||
@ -34,8 +38,13 @@ export const SelectableItem = ({
|
||||
}, [isSelectedItemId]);
|
||||
|
||||
return (
|
||||
<StyledContainer className={className} ref={scrollRef}>
|
||||
{children}
|
||||
</StyledContainer>
|
||||
<>
|
||||
{isSelectedItemId && isDefined(onEnter) && (
|
||||
<SelectableListItemHotkeyEffect itemId={itemId} onEnter={onEnter} />
|
||||
)}
|
||||
<StyledContainer className={className} ref={scrollRef}>
|
||||
{children}
|
||||
</StyledContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,19 @@
|
||||
import { useSelectableListListenToEnterHotkeyOnItem } from '@/ui/layout/selectable-list/hooks/useSelectableListListenToEnterHotkeyOnItem';
|
||||
import { useSelectableListContextOrThrow } from '@/ui/layout/selectable-list/states/contexts/SelectableListContext';
|
||||
|
||||
export const SelectableListItemHotkeyEffect = ({
|
||||
itemId,
|
||||
onEnter,
|
||||
}: {
|
||||
itemId: string;
|
||||
onEnter: () => void;
|
||||
}) => {
|
||||
const { hotkeyScope } = useSelectableListContextOrThrow();
|
||||
|
||||
useSelectableListListenToEnterHotkeyOnItem({
|
||||
hotkeyScope,
|
||||
itemId,
|
||||
onEnter,
|
||||
});
|
||||
return null;
|
||||
};
|
||||
@ -3,7 +3,6 @@ import { useRecoilCallback } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { selectableItemIdsComponentState } from '@/ui/layout/selectable-list/states/selectableItemIdsComponentState';
|
||||
import { selectableListOnEnterComponentState } from '@/ui/layout/selectable-list/states/selectableListOnEnterComponentState';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
@ -147,35 +146,4 @@ export const useSelectableListHotKeys = (
|
||||
hotkeyScope,
|
||||
[],
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.Enter,
|
||||
useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
() => {
|
||||
const selectedItemId = getSnapshotValue(
|
||||
snapshot,
|
||||
selectedItemIdComponentState.atomFamily({
|
||||
instanceId: instanceId,
|
||||
}),
|
||||
);
|
||||
const onEnter = getSnapshotValue(
|
||||
snapshot,
|
||||
selectableListOnEnterComponentState.atomFamily({
|
||||
instanceId: instanceId,
|
||||
}),
|
||||
);
|
||||
|
||||
if (isNonEmptyString(selectedItemId)) {
|
||||
onEnter?.(selectedItemId);
|
||||
}
|
||||
},
|
||||
[instanceId],
|
||||
),
|
||||
hotkeyScope,
|
||||
[],
|
||||
{
|
||||
preventDefault: false,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@ -39,8 +39,5 @@ export const useSelectableListListenToEnterHotkeyOnItem = ({
|
||||
),
|
||||
hotkeyScope,
|
||||
[itemId, onEnter],
|
||||
{
|
||||
preventDefault: false,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
import { createRequiredContext } from '~/utils/createRequiredContext';
|
||||
|
||||
export type SelectableListContextValue = {
|
||||
hotkeyScope: string;
|
||||
};
|
||||
|
||||
export const [SelectableListContextProvider, useSelectableListContextOrThrow] =
|
||||
createRequiredContext<SelectableListContextValue>('SelectableListContext');
|
||||
@ -1,10 +0,0 @@
|
||||
import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
|
||||
export const selectableListOnEnterComponentState = createComponentStateV2<
|
||||
((itemId: string) => void) | undefined
|
||||
>({
|
||||
key: 'selectableListOnEnterComponentState',
|
||||
defaultValue: undefined,
|
||||
componentInstanceContext: SelectableListComponentInstanceContext,
|
||||
});
|
||||
@ -141,7 +141,7 @@ export const WorkflowVariablesDropdownFieldItems = ({
|
||||
<MenuItemSelect
|
||||
key={key}
|
||||
selected={false}
|
||||
hovered={false}
|
||||
focused={false}
|
||||
onClick={() => handleSelectField(key)}
|
||||
text={subStep.label || key}
|
||||
hasSubMenu={!subStep.isLeaf}
|
||||
|
||||
@ -121,7 +121,7 @@ export const WorkflowVariablesDropdownObjectItems = ({
|
||||
{shouldDisplaySubStepObject && displayedSubStepObject?.label && (
|
||||
<MenuItemSelect
|
||||
selected={false}
|
||||
hovered={false}
|
||||
focused={false}
|
||||
onClick={handleSelectObject}
|
||||
text={displayedSubStepObject.label}
|
||||
hasSubMenu={false}
|
||||
@ -136,7 +136,7 @@ export const WorkflowVariablesDropdownObjectItems = ({
|
||||
<MenuItemSelect
|
||||
key={key}
|
||||
selected={false}
|
||||
hovered={false}
|
||||
focused={false}
|
||||
onClick={() => handleSelectField(key)}
|
||||
text={value.label || key}
|
||||
hasSubMenu={!value.isLeaf}
|
||||
|
||||
@ -55,7 +55,7 @@ export const WorkflowVariablesDropdownWorkflowStepItems = ({
|
||||
<MenuItemSelect
|
||||
key={`step-${item.id}`}
|
||||
selected={false}
|
||||
hovered={false}
|
||||
focused={false}
|
||||
onClick={() => onSelect(item.id)}
|
||||
text={item.name}
|
||||
LeftIcon={item.icon ? getIcon(item.icon) : undefined}
|
||||
|
||||
@ -35,6 +35,7 @@ export type MenuItemProps = {
|
||||
text: ReactNode;
|
||||
contextualText?: ReactNode;
|
||||
hasSubMenu?: boolean;
|
||||
focused?: boolean;
|
||||
};
|
||||
|
||||
export const MenuItem = ({
|
||||
@ -53,6 +54,7 @@ export const MenuItem = ({
|
||||
contextualText,
|
||||
hasSubMenu = false,
|
||||
disabled = false,
|
||||
focused = false,
|
||||
}: MenuItemProps) => {
|
||||
const theme = useTheme();
|
||||
const showIconButtons = Array.isArray(iconButtons) && iconButtons.length > 0;
|
||||
@ -75,6 +77,7 @@ export const MenuItem = ({
|
||||
isIconDisplayedOnHoverOnly={isIconDisplayedOnHoverOnly}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
focused={focused}
|
||||
>
|
||||
<StyledMenuItemLeftContent>
|
||||
<MenuItemLeftContent
|
||||
|
||||
@ -7,19 +7,11 @@ import { MenuItemLeftContent } from '../internals/components/MenuItemLeftContent
|
||||
import { StyledMenuItemBase } from '../internals/components/StyledMenuItemBase';
|
||||
|
||||
export const StyledMenuItemSelect = styled(StyledMenuItemBase)<{
|
||||
selected: boolean;
|
||||
disabled?: boolean;
|
||||
hovered?: boolean;
|
||||
focused?: boolean;
|
||||
}>`
|
||||
${({ theme, selected, disabled, hovered }) => {
|
||||
if (selected) {
|
||||
return css`
|
||||
background: ${theme.background.transparent.light};
|
||||
&:hover {
|
||||
background: ${theme.background.transparent.medium};
|
||||
}
|
||||
`;
|
||||
} else if (disabled === true) {
|
||||
${({ theme, disabled, focused }) => {
|
||||
if (disabled === true) {
|
||||
return css`
|
||||
background: inherit;
|
||||
&:hover {
|
||||
@ -30,7 +22,7 @@ export const StyledMenuItemSelect = styled(StyledMenuItemBase)<{
|
||||
|
||||
cursor: default;
|
||||
`;
|
||||
} else if (hovered === true) {
|
||||
} else if (focused === true) {
|
||||
return css`
|
||||
background: ${theme.background.transparent.light};
|
||||
`;
|
||||
@ -46,7 +38,7 @@ type MenuItemSelectProps = {
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
disabled?: boolean;
|
||||
hovered?: boolean;
|
||||
focused?: boolean;
|
||||
hasSubMenu?: boolean;
|
||||
contextualText?: ReactNode;
|
||||
};
|
||||
@ -59,7 +51,7 @@ export const MenuItemSelect = ({
|
||||
className,
|
||||
onClick,
|
||||
disabled,
|
||||
hovered,
|
||||
focused,
|
||||
hasSubMenu = false,
|
||||
contextualText,
|
||||
}: MenuItemSelectProps) => {
|
||||
@ -69,9 +61,8 @@ export const MenuItemSelect = ({
|
||||
<StyledMenuItemSelect
|
||||
onClick={onClick}
|
||||
className={className}
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
hovered={hovered}
|
||||
focused={focused}
|
||||
role="option"
|
||||
aria-selected={selected}
|
||||
aria-disabled={disabled}
|
||||
|
||||
@ -17,7 +17,7 @@ type MenuItemSelectAvatarProps = {
|
||||
className?: string;
|
||||
onClick?: (event?: React.MouseEvent) => void;
|
||||
disabled?: boolean;
|
||||
hovered?: boolean;
|
||||
focused?: boolean;
|
||||
testId?: string;
|
||||
};
|
||||
|
||||
@ -28,7 +28,7 @@ export const MenuItemSelectAvatar = ({
|
||||
className,
|
||||
onClick,
|
||||
disabled,
|
||||
hovered,
|
||||
focused,
|
||||
testId,
|
||||
}: MenuItemSelectAvatarProps) => {
|
||||
const theme = useTheme();
|
||||
@ -37,9 +37,8 @@ export const MenuItemSelectAvatar = ({
|
||||
<StyledMenuItemSelect
|
||||
onClick={onClick}
|
||||
className={className}
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
hovered={hovered}
|
||||
focused={focused}
|
||||
data-testid={testId}
|
||||
role="option"
|
||||
aria-selected={selected}
|
||||
|
||||
@ -15,7 +15,7 @@ type MenuItemSelectColorProps = {
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
disabled?: boolean;
|
||||
hovered?: boolean;
|
||||
focused?: boolean;
|
||||
color: ThemeColor;
|
||||
variant?: ColorSampleVariant;
|
||||
};
|
||||
@ -39,7 +39,7 @@ export const MenuItemSelectColor = ({
|
||||
className,
|
||||
onClick,
|
||||
disabled,
|
||||
hovered,
|
||||
focused,
|
||||
variant = 'default',
|
||||
}: MenuItemSelectColorProps) => {
|
||||
const theme = useTheme();
|
||||
@ -48,9 +48,8 @@ export const MenuItemSelectColor = ({
|
||||
<StyledMenuItemSelect
|
||||
onClick={onClick}
|
||||
className={className}
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
hovered={hovered}
|
||||
focused={focused}
|
||||
>
|
||||
<StyledMenuItemLeftContent>
|
||||
<ColorSample colorName={color} variant={variant} />
|
||||
|
||||
@ -11,7 +11,8 @@ import { ThemeColor } from '@ui/theme';
|
||||
import { StyledMenuItemSelect } from './MenuItemSelect';
|
||||
|
||||
type MenuItemSelectTagProps = {
|
||||
selected: boolean;
|
||||
selected?: boolean;
|
||||
focused?: boolean;
|
||||
isKeySelected?: boolean;
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
@ -24,6 +25,7 @@ type MenuItemSelectTagProps = {
|
||||
export const MenuItemSelectTag = ({
|
||||
color,
|
||||
selected,
|
||||
focused,
|
||||
isKeySelected,
|
||||
className,
|
||||
onClick,
|
||||
@ -36,7 +38,7 @@ export const MenuItemSelectTag = ({
|
||||
<StyledMenuItemSelect
|
||||
onClick={onClick}
|
||||
className={className}
|
||||
selected={selected}
|
||||
focused={focused}
|
||||
isKeySelected={isKeySelected}
|
||||
>
|
||||
<StyledMenuItemLeftContent>
|
||||
|
||||
@ -17,6 +17,7 @@ const StyledToggleContainer = styled.label`
|
||||
`;
|
||||
|
||||
type MenuItemToggleProps = {
|
||||
focused?: boolean;
|
||||
LeftIcon?: IconComponent;
|
||||
toggled: boolean;
|
||||
text: string;
|
||||
@ -26,6 +27,7 @@ type MenuItemToggleProps = {
|
||||
};
|
||||
|
||||
export const MenuItemToggle = ({
|
||||
focused,
|
||||
LeftIcon,
|
||||
text,
|
||||
toggled,
|
||||
@ -35,7 +37,7 @@ export const MenuItemToggle = ({
|
||||
}: MenuItemToggleProps) => {
|
||||
const inputId = useId();
|
||||
return (
|
||||
<StyledMenuItemBase className={className}>
|
||||
<StyledMenuItemBase className={className} focused={focused}>
|
||||
<StyledToggleContainer htmlFor={inputId}>
|
||||
<MenuItemLeftContent LeftIcon={LeftIcon} text={text} />
|
||||
<StyledMenuItemRightContent>
|
||||
|
||||
@ -13,6 +13,7 @@ export type MenuItemBaseProps = {
|
||||
isHoverBackgroundDisabled?: boolean;
|
||||
hovered?: boolean;
|
||||
disabled?: boolean;
|
||||
focused?: boolean;
|
||||
};
|
||||
|
||||
export const StyledMenuItemBase = styled.div<MenuItemBaseProps>`
|
||||
@ -72,6 +73,12 @@ export const StyledMenuItemBase = styled.div<MenuItemBaseProps>`
|
||||
}
|
||||
}}
|
||||
|
||||
${({ focused, theme }) =>
|
||||
focused &&
|
||||
css`
|
||||
background: ${theme.background.transparent.light};
|
||||
`};
|
||||
|
||||
position: relative;
|
||||
user-select: none;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user