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:
Raphaël Bosi
2025-04-25 18:55:39 +02:00
committed by GitHub
co-authored by Lucas Bordeau
parent 0b1b81429e
commit f201091c68
61 changed files with 1195 additions and 761 deletions
@@ -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 (
<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>
<SelectableList
hotkeyScope={ActionMenuDropdownHotkeyScope.ActionMenuDropdown}
selectableItemIdArray={selectedItemIdArray}
selectableListInstanceId={dropdownId}
>
{recordIndexActions.map((action) => (
<ActionComponent action={action} key={action.key} />
))}
<MenuItem
<SelectableListItem
itemId="more-actions"
key="more-actions"
LeftIcon={IconLayoutSidebarRightExpand}
onClick={() => {
closeDropdown();
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>
</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,6 +43,10 @@ export const ResetContextToSelectionCommandButton = () => {
}
return (
<SelectableListItem
itemId={RESET_CONTEXT_TO_SELECTION}
onEnter={resetPreviousCommandMenuContext}
>
<CommandMenuItem
id={RESET_CONTEXT_TO_SELECTION}
Icon={IconArrowBackUp}
@@ -54,5 +59,6 @@ export const ResetContextToSelectionCommandButton = () => {
}
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 }) =>
() => {
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>
<SelectableList
hotkeyScope={dropdownId}
selectableItemIdArray={operandsForFilterType.map(
(operand) => operand,
)}
selectableListInstanceId={dropdownId}
>
{operandsForFilterType.map((filterOperand, index) => (
<MenuItem
<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,27 +115,48 @@ export const AdvancedFilterSubFieldSelectMenu = ({
{getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)}
</DropdownMenuHeader>
<DropdownMenuItemsContainer>
<MenuItem
<SelectableList
hotkeyScope={advancedFilterFieldSelectDropdownId}
selectableItemIdArray={selectableItemIdArray}
selectableListInstanceId={advancedFilterFieldSelectDropdownId}
>
<SelectableListItem
itemId={'-1'}
key={`select-filter-${-1}`}
testId={`select-filter-${-1}`}
onEnter={() => {
handleSelectFilter(fieldMetadataItemUsedInDropdown);
}}
>
<MenuItem
focused={selectedItemId === '-1'}
onClick={() => {
handleSelectFilter(fieldMetadataItemUsedInDropdown);
}}
LeftIcon={IconApps}
text={`Any ${getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)} field`}
/>
</SelectableListItem>
{subFieldsAreFilterable &&
options.map((subFieldName, index) => (
<MenuItem
<SelectableListItem
itemId={subFieldName}
key={`select-filter-${index}`}
onEnter={() => {
handleSelectFilter(
fieldMetadataItemUsedInDropdown,
subFieldName,
);
}}
>
<MenuItem
focused={selectedItemId === subFieldName}
key={`select-filter-${index}`}
testId={`select-filter-${index}`}
onClick={() => {
if (isDefined(fieldMetadataItemUsedInDropdown)) {
handleSelectFilter(
fieldMetadataItemUsedInDropdown,
subFieldName,
);
}
}}
text={getCompositeSubFieldLabel(
objectFilterDropdownSubMenuFieldType,
@@ -127,7 +164,9 @@ export const AdvancedFilterSubFieldSelectMenu = ({
)}
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}`}
>
{visibleColumnsFieldMetadataItems.map((visibleFieldMetadataItem) => (
<ObjectFilterDropdownFilterSelectMenuItem
key={visibleFieldMetadataItem.id}
fieldMetadataItemToSelect={visibleFieldMetadataItem}
/>
</SelectableItem>
),
)}
))}
{shouldShowSeparator && <DropdownMenuSeparator />}
{hiddenColumnsFieldMetadataItems.map(
(hiddenFieldMetadataItem, index) => (
<SelectableItem
itemId={hiddenFieldMetadataItem.id}
key={`hidden-select-filter-${index}`}
>
{hiddenColumnsFieldMetadataItems.map((hiddenFieldMetadataItem) => (
<ObjectFilterDropdownFilterSelectMenuItem
key={hiddenFieldMetadataItem.id}
fieldMetadataItemToSelect={hiddenFieldMetadataItem}
/>
</SelectableItem>
),
)}
))}
</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,18 +179,46 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => {
}
/> */}
<DropdownMenuItemsContainer>
<SelectableList
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
selectableItemIdArray={['-1', ...options]}
selectableListInstanceId={OBJECT_FILTER_DROPDOWN_ID}
>
<SelectableListItem
itemId={'-1'}
key={`select-filter-${-1}`}
onEnter={() => {
handleSelectFilter(fieldMetadataItemUsedInDropdown);
}}
>
<MenuItem
focused={selectedItemId === '-1'}
key={`select-filter-${-1}`}
testId={`select-filter-${-1}`}
onClick={() => {
handleSelectFilter(fieldMetadataItemUsedInDropdown);
}}
LeftIcon={IconApps}
text={`Any ${getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)} field`}
text={`Any ${getFilterableFieldTypeLabel(
objectFilterDropdownSubMenuFieldType,
)} field`}
/>
</SelectableListItem>
{subFieldsAreFilterable &&
options.map((subFieldName, index) => (
<SelectableListItem
itemId={subFieldName}
key={`select-filter-${index}`}
onEnter={() => {
handleSelectFilter(
fieldMetadataItemUsedInDropdown,
subFieldName,
);
}}
>
<MenuItem
focused={selectedItemId === subFieldName}
key={`select-filter-${index}`}
testId={`select-filter-${index}`}
onClick={() => {
@@ -198,7 +235,9 @@ export const ObjectFilterDropdownFilterSelectCompositeFieldSubMenu = () => {
)}
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}
<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,22 +103,43 @@ export const ObjectOptionsDropdownLayoutContent = () => {
>
{t`Layout`}
</DropdownMenuHeader>
{!!currentView && (
<DropdownMenuItemsContainer>
<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 ? (
<>
@@ -117,9 +155,16 @@ export const ObjectOptionsDropdownLayoutContent = () => {
selected={currentView?.type === ViewType.Kanban}
onClick={handleSelectKanbanViewType}
/>
</SelectableListItem>
<DropdownMenuSeparator />
<SelectableListItem
itemId={ViewOpenRecordInType.SIDE_PANEL}
onEnter={() => {
onContentChange('layoutOpenIn');
}}
>
<MenuItem
onClick={() => onContentChange('layoutOpenIn')}
focused={selectedItemId === ViewOpenRecordInType.SIDE_PANEL}
LeftIcon={
recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL
? IconLayoutSidebarRight
@@ -133,9 +178,19 @@ export const ObjectOptionsDropdownLayoutContent = () => {
}
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')
@@ -146,8 +201,19 @@ export const ObjectOptionsDropdownLayoutContent = () => {
contextualText={recordGroupFieldMetadata?.label}
hasSubMenu
/>
</SelectableListItem>
<SelectableListItem
itemId={'Compact view'}
onEnter={() => {
setAndPersistIsCompactModeActive(
!isCompactModeActive,
currentView,
);
}}
>
<MenuItemToggle
focused={selectedItemId === 'Compact view'}
LeftIcon={IconBaselineDensitySmall}
onToggleChange={() =>
setAndPersistIsCompactModeActive(
@@ -159,8 +225,10 @@ export const ObjectOptionsDropdownLayoutContent = () => {
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,10 +50,27 @@ export const ObjectOptionsDropdownLayoutOpenInContent = () => {
{t`Open in`}
</DropdownMenuHeader>
<DropdownMenuItemsContainer>
<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}
selected={
recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL
}
focused={selectedItemId === ViewOpenRecordInType.SIDE_PANEL}
onClick={() =>
setAndPersistOpenRecordIn(
ViewOpenRecordInType.SIDE_PANEL,
@@ -45,6 +78,16 @@ export const ObjectOptionsDropdownLayoutOpenInContent = () => {
)
}
/>
</SelectableListItem>
<SelectableListItem
itemId={ViewOpenRecordInType.RECORD_PAGE}
onEnter={() =>
setAndPersistOpenRecordIn(
ViewOpenRecordInType.RECORD_PAGE,
currentView,
)
}
>
<MenuItemSelect
LeftIcon={IconLayoutNavbar}
text={t`Record Page`}
@@ -57,7 +100,10 @@ export const ObjectOptionsDropdownLayoutOpenInContent = () => {
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,34 +79,75 @@ 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 />
<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)}
LeftIcon={viewTypeIconMapping(
currentView?.type ?? ViewType.Table,
)}
text={t`Layout`}
contextualText={`${capitalize(currentView?.type ?? '')}`}
hasSubMenu
/>
</SelectableListItem>
</DropdownMenuItemsContainer>
<DropdownMenuSeparator />
<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')
@@ -118,6 +163,7 @@ export const ObjectOptionsDropdownMenuContent = () => {
hasSubMenu
disabled={isDefaultView}
/>
</SelectableListItem>
</div>
{!isGroupByEnabled && (
<AppTooltip
@@ -130,7 +176,20 @@ export const ObjectOptionsDropdownMenuContent = () => {
)}
<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);
@@ -143,13 +202,20 @@ export const ObjectOptionsDropdownMenuContent = () => {
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
@@ -161,6 +227,7 @@ export const ObjectOptionsDropdownMenuContent = () => {
/>
)}
</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,12 +70,31 @@ export const ObjectOptionsDropdownRecordGroupSortContent = () => {
Sort
</DropdownMenuHeader>
<DropdownMenuItemsContainer>
<SelectableList
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
hotkeyScope={TableOptionsHotkeyScope.Dropdown}
selectableItemIdArray={selectableItemIdArray}
>
<SelectableListItem
itemId={RecordGroupSort.Manual}
onEnter={() => handleRecordGroupSortChange(RecordGroupSort.Manual)}
>
<MenuItemSelect
onClick={() => handleRecordGroupSortChange(RecordGroupSort.Manual)}
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)
@@ -67,7 +102,15 @@ export const ObjectOptionsDropdownRecordGroupSortContent = () => {
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)
@@ -75,7 +118,10 @@ export const ObjectOptionsDropdownRecordGroupSortContent = () => {
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>
<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"
/>
</SelectableListItem>
</SelectableList>
</DropdownMenuItemsContainer>
{visibleRecordGroupIds.length > 0 && (
<>
@@ -145,11 +185,16 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
<>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer scrollable={false}>
<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) => (
<SelectableListItem
key={visibleFieldMetadataItem.id}
itemId={visibleFieldMetadataItem.id}
onEnter={() => handleAddSort(visibleFieldMetadataItem)}
>
<MenuItem
focused={selectedItemId === visibleFieldMetadataItem.id}
testId={`visible-select-sort-${index}`}
key={index}
onClick={() => handleAddSort(visibleFieldMetadataItem)}
LeftIcon={getIcon(visibleFieldMetadataItem.icon)}
text={visibleFieldMetadataItem.label}
/>
</SelectableListItem>
),
)}
{shouldShowSeparator && <DropdownMenuSeparator />}
{hiddenFieldMetadataItems.map((hiddenFieldMetadataItem, index) => (
<SelectableListItem
key={hiddenFieldMetadataItem.id}
itemId={hiddenFieldMetadataItem.id}
onEnter={() => handleAddSort(hiddenFieldMetadataItem)}
>
<MenuItem
focused={selectedItemId === hiddenFieldMetadataItem.id}
testId={`hidden-select-sort-${index}`}
key={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(
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}>
<MenuItemSelectAvatar
<StyledSelectableItem
itemId={record.id}
key={record.id}
onEnter={() => {
onRecordSelected(record);
}}
>
<MenuItemSelectAvatar
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,8 +121,15 @@ export const SingleRecordPickerMenuItems = ({
case 'select-none': {
return (
emptyLabel && (
<MenuItemSelect
<SelectableListItem
key={record.id}
itemId={record.id}
onEnter={() => {
setSelectedRecordId(undefined);
onRecordSelected();
}}
>
<MenuItemSelect
onClick={() => {
setSelectedRecordId(undefined);
onRecordSelected();
@@ -137,8 +137,9 @@ export const SingleRecordPickerMenuItems = ({
LeftIcon={EmptyIcon}
text={emptyLabel}
selected={isUndefined(selectedRecordId)}
hovered={isSelectedSelectNoneButton}
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,23 +94,17 @@ 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 (
<SelectableListItem
itemId={item.id}
onEnter={() => {
resetSelectedItem();
handleItemSelectChange(item, !item.isSelected);
}}
>
<MenuItemMultiSelectAvatar
key={item.id}
selected={item.isSelected}
@@ -130,6 +125,7 @@ export const MultipleSelectDropdown = ({
/>
}
/>
</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,12 +164,26 @@ export const Select = <Value extends SelectValue>({
)}
{!!filteredOptions.length && (
<DropdownMenuItemsContainer hasMaxHeight width={'auto'}>
<SelectableList
hotkeyScope={SelectHotkeyScope.Select}
selectableListInstanceId={dropdownId}
selectableItemIdArray={selectableItemIdArray}
>
{filteredOptions.map((option) => (
<MenuItemSelect
<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);
@@ -166,7 +191,9 @@ export const Select = <Value extends SelectValue>({
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,
}}
>
<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 (
<>
{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;