Remove the heart icon button to add the view as a favorite from the top bar (#8769)

closes #8546 

@Bonapara please check the behaviour, if this is what you were looking
for! ;)
This commit is contained in:
nitin
2024-12-03 18:19:00 +05:30
committed by GitHub
parent 3c7805c6d0
commit 9a65e80566
11 changed files with 233 additions and 112 deletions

View File

@ -38,6 +38,7 @@ export const PageFavoriteFoldersDropdown = ({
onSubmit={closeDropdown}
record={record}
objectNameSingular={objectNameSingular}
dropdownId={dropdownId}
/>
</>
}

View File

@ -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}
/>
</DropdownMenuItemsContainer>
<FavoriteFolderPickerFooter />
<FavoriteFolderPickerFooter dropdownId={dropdownId} />
</DropdownMenu>
);
};

View File

@ -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 (
<StyledFooter>

View File

@ -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<View>(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 (
<PageHeader title={pageHeaderTitle} Icon={Icon}>
<PageHotkeysEffect onAddButtonClick={handleAddButtonClick} />
{isFavoriteFolderEnabled && (
<PageFavoriteFoldersDropdown
record={view}
dropdownId={FAVORITE_FOLDER_PICKER_DROPDOWN_ID}
objectNameSingular="view"
isFavorite={isFavorite}
/>
)}
{shouldDisplayAddButton &&
(isTable ? (
<PageAddButton onClick={handleAddButtonClick} />

View File

@ -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};

View File

@ -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 }}

View File

@ -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 = () => {
</StyledDropdownLabelAdornments>
</StyledDropdownButtonContainer>
}
dropdownComponents={
viewPickerMode === 'list' ? (
<ViewPickerListContent />
) : (
<>
{viewPickerMode === 'create-empty' ||
viewPickerMode === 'create-from-current' ? (
<ViewPickerContentCreateMode />
) : (
<ViewPickerContentEditMode />
)}
<ViewPickerContentEffect />
</>
)
}
dropdownComponents={(() => {
switch (viewPickerMode) {
case 'list':
return <ViewPickerListContent />;
case 'favorite-folders-picker':
return (
isFavoriteFolderEnabled && <ViewPickerFavoriteFoldersDropdown />
);
case 'create-empty':
case 'create-from-current':
return (
<>
<ViewPickerContentCreateMode />
<ViewPickerContentEffect />
</>
);
case 'edit':
return (
<>
<ViewPickerContentEditMode />
<ViewPickerContentEffect />
</>
);
default:
return null;
}
})()}
/>
);
};

View File

@ -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<View>(PrefetchKey.AllViews);
const [viewPickerReferenceViewId] = useRecoilComponentStateV2(
viewPickerReferenceViewIdComponentState,
);
const view = views.find((view) => view.id === viewPickerReferenceViewId);
return (
<FavoriteFolderPickerComponentInstanceContext
favoriteFoldersScopeId={VIEW_PICKER_DROPDOWN_ID}
>
<DropdownScope dropdownScopeId={VIEW_PICKER_DROPDOWN_ID}>
<>
<FavoriteFolderPickerEffect record={view} />
<FavoriteFolderPicker
record={view}
objectNameSingular="view"
dropdownId={VIEW_PICKER_DROPDOWN_ID}
/>
</>
</DropdownScope>
</FavoriteFolderPickerComponentInstanceContext>
);
};

View File

@ -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<HTMLButtonElement>,
event: MouseEvent<HTMLElement>,
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 = () => {
<DropdownMenuItemsContainer>
<DraggableList
onDragEnd={handleDragEnd}
draggableItems={viewsOnCurrentObject.map((view, index) => (
<DraggableItem
key={view.id}
draggableId={view.id}
index={index}
isDragDisabled={viewsOnCurrentObject.length === 1}
itemComponent={
view.key === 'INDEX' ? (
<MenuItemDraggable
key={view.id}
iconButtons={[
{
Icon: IconLock,
},
].filter(isDefined)}
isIconDisplayedOnHoverOnly={false}
onClick={() => handleViewSelect(view.id)}
LeftIcon={getIcon(view.icon)}
text={view.name}
draggableItems={viewsOnCurrentObject.map((view, index) => {
const isIndexView = view.key === 'INDEX';
return (
<DraggableItem
key={view.id}
draggableId={view.id}
index={index}
isDragDisabled={viewsOnCurrentObject.length === 1}
itemComponent={
<ViewPickerOptionDropdown
view={view as View}
handleViewSelect={handleViewSelect}
isIndexView={isIndexView}
onEdit={handleEditViewButtonClick}
/>
) : (
<MenuItemDraggable
key={view.id}
iconButtons={[
{
Icon: IconPencil,
onClick: (event: MouseEvent<HTMLButtonElement>) =>
handleEditViewButtonClick(event, view.id),
accent: 'tertiary' as LightIconButtonAccent,
},
].filter(isDefined)}
isIconDisplayedOnHoverOnly={true}
onClick={() => handleViewSelect(view.id)}
LeftIcon={getIcon(view.icon)}
text={view.name}
/>
)
}
/>
))}
}
/>
);
})}
/>
</DropdownMenuItemsContainer>
<DropdownMenuSeparator />

View File

@ -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<HTMLElement>, 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 (
<>
<MenuItemWithOptionDropdown
text={view.name}
LeftIcon={getIcon(view.icon)}
onClick={() => 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={
<DropdownMenuItemsContainer>
{isIndexView ? (
isFavoriteFolderEnabled && (
<MenuItem
LeftIcon={IconHeart}
text={isFavorite ? 'Manage favorite' : 'Add to Favorite'}
onClick={handleAddToFavorites}
/>
)
) : (
<>
{isFavoriteFolderEnabled && (
<MenuItem
LeftIcon={IconHeart}
text={isFavorite ? 'Manage favorite' : 'Add to Favorite'}
onClick={handleAddToFavorites}
/>
)}
<MenuItem
LeftIcon={IconPencil}
text="Edit"
onClick={(event) => {
onEdit(event, view.id);
closeDropdown();
}}
/>
<MenuItem
LeftIcon={IconTrash}
text="Delete"
onClick={handleDelete}
accent="danger"
/>
</>
)}
</DropdownMenuItemsContainer>
}
/>
</>
);
};

View File

@ -2,4 +2,5 @@ export type ViewPickerMode =
| 'list'
| 'edit'
| 'create-empty'
| 'create-from-current';
| 'create-from-current'
| 'favorite-folders-picker';