diff --git a/packages/twenty-front/src/modules/favorites/components/PageFavoriteFolderDropdown.tsx b/packages/twenty-front/src/modules/favorites/components/PageFavoriteFolderDropdown.tsx
index ba7324c9c..1c5df65d5 100644
--- a/packages/twenty-front/src/modules/favorites/components/PageFavoriteFolderDropdown.tsx
+++ b/packages/twenty-front/src/modules/favorites/components/PageFavoriteFolderDropdown.tsx
@@ -38,6 +38,7 @@ export const PageFavoriteFoldersDropdown = ({
onSubmit={closeDropdown}
record={record}
objectNameSingular={objectNameSingular}
+ dropdownId={dropdownId}
/>
>
}
diff --git a/packages/twenty-front/src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPicker.tsx b/packages/twenty-front/src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPicker.tsx
index 456d36d95..a8b58b34f 100644
--- a/packages/twenty-front/src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPicker.tsx
+++ b/packages/twenty-front/src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPicker.tsx
@@ -20,6 +20,7 @@ type FavoriteFolderPickerProps = {
onSubmit?: () => void;
record?: ObjectRecord;
objectNameSingular: string;
+ dropdownId: string;
};
const NO_FOLDER_ID = 'no-folder';
@@ -28,6 +29,7 @@ export const FavoriteFolderPicker = ({
onSubmit,
record,
objectNameSingular,
+ dropdownId,
}: FavoriteFolderPickerProps) => {
const [isFavoriteFolderCreating, setIsFavoriteFolderCreating] =
useRecoilState(isFavoriteFolderCreatingState);
@@ -97,7 +99,7 @@ export const FavoriteFolderPicker = ({
toggleFolderSelection={toggleFolderSelection}
/>
-
+
);
};
diff --git a/packages/twenty-front/src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerFooter.tsx b/packages/twenty-front/src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerFooter.tsx
index 0d9f3d379..b43b045a2 100644
--- a/packages/twenty-front/src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerFooter.tsx
+++ b/packages/twenty-front/src/modules/favorites/favorite-folder-picker/components/FavoriteFolderPickerFooter.tsx
@@ -1,4 +1,3 @@
-import { FAVORITE_FOLDER_PICKER_DROPDOWN_ID } from '@/favorites/favorite-folder-picker/constants/FavoriteFolderPickerDropdownId';
import { isFavoriteFolderCreatingState } from '@/favorites/states/isFavoriteFolderCreatingState';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
@@ -10,13 +9,16 @@ import { useRecoilState, useSetRecoilState } from 'recoil';
import { IconPlus, MenuItem } from 'twenty-ui';
const StyledFooter = styled.div`
- background: ${({ theme }) => theme.background.primary};
border-bottom-left-radius: ${({ theme }) => theme.border.radius.md};
border-bottom-right-radius: ${({ theme }) => theme.border.radius.md};
border-top: 1px solid ${({ theme }) => theme.border.color.light};
`;
-export const FavoriteFolderPickerFooter = () => {
+export const FavoriteFolderPickerFooter = ({
+ dropdownId,
+}: {
+ dropdownId: string;
+}) => {
const [, setIsFavoriteFolderCreating] = useRecoilState(
isFavoriteFolderCreatingState,
);
@@ -25,7 +27,7 @@ export const FavoriteFolderPickerFooter = () => {
);
const { openNavigationSection } = useNavigationSection('Favorites');
const theme = useTheme();
- const { closeDropdown } = useDropdown(FAVORITE_FOLDER_PICKER_DROPDOWN_ID);
+ const { closeDropdown } = useDropdown(dropdownId);
return (
diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx
index 94c85d7b0..3eeec0aea 100644
--- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx
@@ -1,21 +1,12 @@
-import { PageFavoriteFoldersDropdown } from '@/favorites/components/PageFavoriteFolderDropdown';
-import { FAVORITE_FOLDER_PICKER_DROPDOWN_ID } from '@/favorites/favorite-folder-picker/constants/FavoriteFolderPickerDropdownId';
-import { useFavorites } from '@/favorites/hooks/useFavorites';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { isObjectMetadataReadOnly } from '@/object-metadata/utils/isObjectMetadataReadOnly';
import { RecordIndexPageKanbanAddButton } from '@/object-record/record-index/components/RecordIndexPageKanbanAddButton';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState';
-import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
-import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { PageAddButton } from '@/ui/layout/page/components/PageAddButton';
import { PageHeader } from '@/ui/layout/page/components/PageHeader';
import { PageHotkeysEffect } from '@/ui/layout/page/components/PageHotkeysEffect';
-import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
-import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
-import { View } from '@/views/types/View';
import { ViewType } from '@/views/types/ViewType';
-import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
import { useIcons } from 'twenty-ui';
@@ -24,27 +15,10 @@ import { capitalize } from '~/utils/string/capitalize';
export const RecordIndexPageHeader = () => {
const { findObjectMetadataItemByNamePlural } =
useFilteredObjectMetadataItems();
- const isFavoriteFolderEnabled = useIsFeatureEnabled(
- 'IS_FAVORITE_FOLDER_ENABLED',
- );
- const { objectNamePlural, onCreateRecord, recordIndexId } = useContext(
+ const { objectNamePlural, onCreateRecord } = useContext(
RecordIndexRootPropsContext,
);
- const { records: views } = usePrefetchedData(PrefetchKey.AllViews);
- const currentViewId = useRecoilComponentValueV2(
- currentViewIdComponentState,
- recordIndexId,
- );
-
- const view = views.find((view) => view.id === currentViewId);
-
- const { sortedFavorites: favorites } = useFavorites();
-
- const isFavorite = favorites.some(
- (favorite) =>
- favorite.recordId === currentViewId && favorite.workspaceMemberId,
- );
const objectMetadataItem =
findObjectMetadataItemByNamePlural(objectNamePlural);
@@ -72,14 +46,6 @@ export const RecordIndexPageHeader = () => {
return (
- {isFavoriteFolderEnabled && (
-
- )}
{shouldDisplayAddButton &&
(isTable ? (
diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuSearchInput.tsx b/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuSearchInput.tsx
index 00edc11ce..54c54c136 100644
--- a/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuSearchInput.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuSearchInput.tsx
@@ -6,7 +6,7 @@ import { TEXT_INPUT_STYLE } from 'twenty-ui';
const StyledDropdownMenuSearchInputContainer = styled.div`
align-items: center;
--vertical-padding: ${({ theme }) => theme.spacing(2)};
-
+ border-radius: ${({ theme }) => theme.border.radius.sm};
display: flex;
background: ${({ theme }) => theme.background.transparent.secondary};
backdrop-filter: ${({ theme }) => theme.blur.medium};
diff --git a/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemWithOptionDropdown.tsx b/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemWithOptionDropdown.tsx
index 77f5c95ab..a9836e0cb 100644
--- a/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemWithOptionDropdown.tsx
+++ b/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemWithOptionDropdown.tsx
@@ -1,6 +1,7 @@
import { SelectHotkeyScope } from '@/ui/input/types/SelectHotkeyScope';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { useTheme } from '@emotion/react';
+import { Placement } from '@floating-ui/react';
import { FunctionComponent, MouseEvent, ReactElement, ReactNode } from 'react';
import {
IconChevronRight,
@@ -36,6 +37,7 @@ export type MenuItemWithOptionDropdownProps = {
testId?: string;
text: ReactNode;
hasSubMenu?: boolean;
+ dropdownPlacement?: Placement;
};
// TODO: refactor this
@@ -53,6 +55,7 @@ export const MenuItemWithOptionDropdown = ({
testId,
text,
hasSubMenu = false,
+ dropdownPlacement = 'bottom-end',
}: MenuItemWithOptionDropdownProps) => {
const theme = useTheme();
@@ -86,6 +89,7 @@ export const MenuItemWithOptionDropdown = ({
accent="tertiary"
/>
}
+ dropdownPlacement={dropdownPlacement}
dropdownComponents={dropdownContent}
dropdownId={dropdownId}
dropdownHotkeyScope={{ scope: SelectHotkeyScope.Select }}
diff --git a/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerDropdown.tsx b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerDropdown.tsx
index 990a5088c..fa8e7ed21 100644
--- a/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerDropdown.tsx
+++ b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerDropdown.tsx
@@ -17,10 +17,12 @@ import { ViewsHotkeyScope } from '@/views/types/ViewsHotkeyScope';
import { ViewPickerContentCreateMode } from '@/views/view-picker/components/ViewPickerContentCreateMode';
import { ViewPickerContentEditMode } from '@/views/view-picker/components/ViewPickerContentEditMode';
import { ViewPickerContentEffect } from '@/views/view-picker/components/ViewPickerContentEffect';
+import { ViewPickerFavoriteFoldersDropdown } from '@/views/view-picker/components/ViewPickerFavoriteFoldersDropdown';
import { ViewPickerListContent } from '@/views/view-picker/components/ViewPickerListContent';
import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId';
import { useUpdateViewFromCurrentState } from '@/views/view-picker/hooks/useUpdateViewFromCurrentState';
import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode';
+import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { isDefined } from '~/utils/isDefined';
const StyledDropdownLabelAdornments = styled.span`
@@ -49,6 +51,9 @@ const StyledViewName = styled.span`
export const ViewPickerDropdown = () => {
const theme = useTheme();
+ const isFavoriteFolderEnabled = useIsFeatureEnabled(
+ 'IS_FAVORITE_FOLDER_ENABLED',
+ );
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
@@ -99,21 +104,33 @@ export const ViewPickerDropdown = () => {
}
- dropdownComponents={
- viewPickerMode === 'list' ? (
-
- ) : (
- <>
- {viewPickerMode === 'create-empty' ||
- viewPickerMode === 'create-from-current' ? (
-
- ) : (
-
- )}
-
- >
- )
- }
+ dropdownComponents={(() => {
+ switch (viewPickerMode) {
+ case 'list':
+ return ;
+ case 'favorite-folders-picker':
+ return (
+ isFavoriteFolderEnabled &&
+ );
+ case 'create-empty':
+ case 'create-from-current':
+ return (
+ <>
+
+
+ >
+ );
+ case 'edit':
+ return (
+ <>
+
+
+ >
+ );
+ default:
+ return null;
+ }
+ })()}
/>
);
};
diff --git a/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerFavoriteFoldersDropdown.tsx b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerFavoriteFoldersDropdown.tsx
new file mode 100644
index 000000000..470337e77
--- /dev/null
+++ b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerFavoriteFoldersDropdown.tsx
@@ -0,0 +1,36 @@
+import { FavoriteFolderPicker } from '@/favorites/favorite-folder-picker/components/FavoriteFolderPicker';
+import { FavoriteFolderPickerEffect } from '@/favorites/favorite-folder-picker/components/FavoriteFolderPickerEffect';
+import { FavoriteFolderPickerComponentInstanceContext } from '@/favorites/favorite-folder-picker/scopes/FavoriteFolderPickerScope';
+import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
+import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
+import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
+import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
+import { View } from '@/views/types/View';
+import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId';
+import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/states/viewPickerReferenceViewIdComponentState';
+
+export const ViewPickerFavoriteFoldersDropdown = () => {
+ const { records: views } = usePrefetchedData(PrefetchKey.AllViews);
+ const [viewPickerReferenceViewId] = useRecoilComponentStateV2(
+ viewPickerReferenceViewIdComponentState,
+ );
+
+ const view = views.find((view) => view.id === viewPickerReferenceViewId);
+
+ return (
+
+
+ <>
+
+
+ >
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerListContent.tsx b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerListContent.tsx
index 002004488..85e061987 100644
--- a/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerListContent.tsx
+++ b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerListContent.tsx
@@ -1,26 +1,18 @@
import styled from '@emotion/styled';
import { DropResult } from '@hello-pangea/dnd';
import { MouseEvent, useCallback } from 'react';
-import {
- IconLock,
- IconPencil,
- IconPlus,
- LightIconButtonAccent,
- MenuItem,
- MenuItemDraggable,
- useIcons,
-} from 'twenty-ui';
+import { IconPlus, MenuItem } from 'twenty-ui';
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
import { DraggableList } from '@/ui/layout/draggable-list/components/DraggableList';
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 { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { useChangeView } from '@/views/hooks/useChangeView';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { useUpdateView } from '@/views/hooks/useUpdateView';
-import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId';
+import { View } from '@/views/types/View';
+import { ViewPickerOptionDropdown } from '@/views/view-picker/components/ViewPickerOptionDropdown';
import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode';
import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/states/viewPickerReferenceViewIdComponentState';
import { moveArrayItem } from '~/utils/array/moveArrayItem';
@@ -39,13 +31,11 @@ export const ViewPickerListContent = () => {
const { setViewPickerMode } = useViewPickerMode();
- const { closeDropdown } = useDropdown(VIEW_PICKER_DROPDOWN_ID);
const { updateView } = useUpdateView();
const { changeView } = useChangeView();
const handleViewSelect = (viewId: string) => {
changeView(viewId);
- closeDropdown();
};
const handleAddViewButtonClick = () => {
@@ -56,7 +46,7 @@ export const ViewPickerListContent = () => {
};
const handleEditViewButtonClick = (
- event: MouseEvent,
+ event: MouseEvent,
viewId: string,
) => {
event.stopPropagation();
@@ -64,8 +54,6 @@ export const ViewPickerListContent = () => {
setViewPickerMode('edit');
};
- const { getIcon } = useIcons();
-
const handleDragEnd = useCallback(
(result: DropResult) => {
if (!result.destination) return;
@@ -87,46 +75,25 @@ export const ViewPickerListContent = () => {
(
- handleViewSelect(view.id)}
- LeftIcon={getIcon(view.icon)}
- text={view.name}
+ draggableItems={viewsOnCurrentObject.map((view, index) => {
+ const isIndexView = view.key === 'INDEX';
+ return (
+
- ) : (
- ) =>
- handleEditViewButtonClick(event, view.id),
- accent: 'tertiary' as LightIconButtonAccent,
- },
- ].filter(isDefined)}
- isIconDisplayedOnHoverOnly={true}
- onClick={() => handleViewSelect(view.id)}
- LeftIcon={getIcon(view.icon)}
- text={view.name}
- />
- )
- }
- />
- ))}
+ }
+ />
+ );
+ })}
/>
diff --git a/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerOptionDropdown.tsx b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerOptionDropdown.tsx
new file mode 100644
index 000000000..e37906ed1
--- /dev/null
+++ b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerOptionDropdown.tsx
@@ -0,0 +1,125 @@
+import { useCreateFavorite } from '@/favorites/hooks/useCreateFavorite';
+import { useFavorites } from '@/favorites/hooks/useFavorites';
+import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
+import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
+import { MenuItemWithOptionDropdown } from '@/ui/navigation/menu-item/components/MenuItemWithOptionDropdown';
+import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
+import { View } from '@/views/types/View';
+import { useDeleteViewFromCurrentState } from '@/views/view-picker/hooks/useDeleteViewFromCurrentState';
+import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode';
+import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/states/viewPickerReferenceViewIdComponentState';
+import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
+import { useState } from 'react';
+import {
+ IconHeart,
+ IconLock,
+ IconPencil,
+ IconTrash,
+ MenuItem,
+ useIcons,
+} from 'twenty-ui';
+
+type ViewPickerOptionDropdownProps = {
+ isIndexView: boolean;
+ view: View;
+ onEdit: (event: React.MouseEvent, viewId: string) => void;
+ handleViewSelect: (viewId: string) => void;
+};
+
+export const ViewPickerOptionDropdown = ({
+ isIndexView,
+ onEdit,
+ view,
+ handleViewSelect,
+}: ViewPickerOptionDropdownProps) => {
+ const { closeDropdown } = useDropdown(`view-picker-options-${view.id}`);
+ const { getIcon } = useIcons();
+ const [isHovered, setIsHovered] = useState(false);
+ const { deleteViewFromCurrentState } = useDeleteViewFromCurrentState();
+ const setViewPickerReferenceViewId = useSetRecoilComponentStateV2(
+ viewPickerReferenceViewIdComponentState,
+ );
+ const { setViewPickerMode } = useViewPickerMode();
+
+ const isFavoriteFolderEnabled = useIsFeatureEnabled(
+ 'IS_FAVORITE_FOLDER_ENABLED',
+ );
+
+ const { sortedFavorites: favorites } = useFavorites();
+ const { createFavorite } = useCreateFavorite();
+
+ const isFavorite = favorites.some(
+ (favorite) => favorite.recordId === view.id && favorite.workspaceMemberId,
+ );
+
+ const handleDelete = () => {
+ setViewPickerReferenceViewId(view.id);
+ deleteViewFromCurrentState();
+ closeDropdown();
+ };
+
+ const handleAddToFavorites = () => {
+ if (!isFavorite) {
+ createFavorite(view, 'view');
+ }
+ setViewPickerReferenceViewId(view.id);
+ setViewPickerMode('favorite-folders-picker');
+ closeDropdown();
+ };
+
+ return (
+ <>
+ handleViewSelect(view.id)}
+ isIconDisplayedOnHoverOnly={!isIndexView}
+ RightIcon={!isHovered && isIndexView ? IconLock : null}
+ onMouseEnter={() => setIsHovered(true)}
+ onMouseLeave={() => {
+ setIsHovered(false);
+ closeDropdown();
+ }}
+ dropdownPlacement="bottom-start"
+ dropdownId={`view-picker-options-${view.id}`}
+ dropdownContent={
+
+ {isIndexView ? (
+ isFavoriteFolderEnabled && (
+
+ )
+ ) : (
+ <>
+ {isFavoriteFolderEnabled && (
+
+ )}
+
+ }
+ />
+ >
+ );
+};
diff --git a/packages/twenty-front/src/modules/views/view-picker/types/ViewPickerMode.ts b/packages/twenty-front/src/modules/views/view-picker/types/ViewPickerMode.ts
index cc095b1e3..b6f194c9f 100644
--- a/packages/twenty-front/src/modules/views/view-picker/types/ViewPickerMode.ts
+++ b/packages/twenty-front/src/modules/views/view-picker/types/ViewPickerMode.ts
@@ -2,4 +2,5 @@ export type ViewPickerMode =
| 'list'
| 'edit'
| 'create-empty'
- | 'create-from-current';
+ | 'create-from-current'
+ | 'favorite-folders-picker';