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 && ( + + )} + { + onEdit(event, view.id); + closeDropdown(); + }} + /> + + + )} + + } + /> + + ); +}; 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';