diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCustomView.tsx b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCustomView.tsx
new file mode 100644
index 00000000000..8da219a6042
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownCustomView.tsx
@@ -0,0 +1,239 @@
+import { ObjectOptionsDropdownMenuViewName } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuViewName';
+import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId';
+import { useObjectOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsDropdown';
+import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
+import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
+import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
+import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
+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 { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
+import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
+import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
+import { ViewKey } from '@/views/types/ViewKey';
+import { ViewType, viewTypeIconMapping } from '@/views/types/ViewType';
+import { useDeleteViewFromCurrentState } from '@/views/view-picker/hooks/useDeleteViewFromCurrentState';
+import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/states/viewPickerReferenceViewIdComponentState';
+import { useTheme } from '@emotion/react';
+import { useLingui } from '@lingui/react/macro';
+import { capitalize, isDefined } from 'twenty-shared/utils';
+import {
+ AppTooltip,
+ IconCopy,
+ IconLayoutList,
+ IconListDetails,
+ IconTrash,
+} from 'twenty-ui/display';
+import { MenuItem } from 'twenty-ui/navigation';
+
+interface ObjectOptionsDropdownCustomViewProps {
+ onBackToDefault?: () => void;
+}
+
+export const ObjectOptionsDropdownCustomView = ({
+ onBackToDefault,
+}: ObjectOptionsDropdownCustomViewProps) => {
+ const { t } = useLingui();
+ const { recordIndexId, objectMetadataItem, onContentChange, closeDropdown } =
+ useObjectOptionsDropdown();
+
+ const { currentView } = useGetCurrentViewOnly();
+
+ const customViewData = currentView
+ ? {
+ ...currentView,
+ key: ViewKey.Custom,
+ name: currentView.name || 'Custom View',
+ }
+ : null;
+
+ const recordGroupFieldMetadata = useRecoilComponentValue(
+ recordGroupFieldMetadataComponentState,
+ );
+
+ const isDefaultView = currentView?.key === ViewKey.Index;
+
+ const { visibleBoardFields } = useObjectOptionsForBoard({
+ objectNameSingular: objectMetadataItem.nameSingular,
+ recordBoardId: recordIndexId,
+ viewBarId: recordIndexId,
+ });
+
+ const { deleteViewFromCurrentState } = useDeleteViewFromCurrentState();
+ const setViewPickerReferenceViewId = useSetRecoilComponentState(
+ viewPickerReferenceViewIdComponentState,
+ );
+
+ const handleDelete = () => {
+ if (!customViewData?.id) {
+ return;
+ }
+ setViewPickerReferenceViewId(customViewData?.id);
+ deleteViewFromCurrentState();
+ closeDropdown();
+ onBackToDefault?.();
+ };
+
+ const theme = useTheme();
+ const { enqueueSuccessSnackBar } = useSnackBar();
+
+ const selectableItemIdArray = [
+ 'Layout',
+ 'Fields',
+ 'Group',
+ 'Copy link to view',
+ 'Delete view',
+ ];
+
+ const selectedItemId = useRecoilComponentValue(
+ selectedItemIdComponentState,
+ OBJECT_OPTIONS_DROPDOWN_ID,
+ );
+
+ if (!customViewData) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+ onContentChange('layout')}
+ >
+
+
+
+
+ onContentChange('fields')}
+ >
+
+
+ {isDefaultView && (
+
+ )}
+
+
+
+ {
+ const currentUrl = window.location.href;
+ navigator.clipboard.writeText(currentUrl);
+ enqueueSuccessSnackBar({
+ message: t`Link copied to clipboard`,
+ options: {
+ icon: ,
+ duration: 2000,
+ },
+ });
+ }}
+ >
+
+
+ {isDefaultView && (
+
+ )}
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownDefaultView.tsx b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownDefaultView.tsx
new file mode 100644
index 00000000000..e1fa63ab709
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownDefaultView.tsx
@@ -0,0 +1,128 @@
+import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId';
+import { useObjectOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsDropdown';
+import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
+import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
+import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
+import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
+import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
+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 { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
+import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
+import { useOpenCreateViewDropdown } from '@/views/hooks/useOpenCreateViewDropown';
+import { useLingui } from '@lingui/react/macro';
+import {
+ IconCopy,
+ IconLayout,
+ IconListDetails,
+ IconLock,
+ useIcons,
+} from 'twenty-ui/display';
+import { MenuItem } from 'twenty-ui/navigation';
+import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
+
+export const ObjectOptionsDropdownDefaultView = () => {
+ const { t } = useLingui();
+ const { recordIndexId, objectMetadataItem, onContentChange } =
+ useObjectOptionsDropdown();
+
+ const { currentView } = useGetCurrentViewOnly();
+
+ const { visibleBoardFields } = useObjectOptionsForBoard({
+ objectNameSingular: objectMetadataItem.nameSingular,
+ recordBoardId: recordIndexId,
+ viewBarId: recordIndexId,
+ });
+
+ const selectableItemIdArray = [
+ 'Fields',
+ 'Copy link to view',
+ 'Create custom view',
+ ];
+
+ const selectedItemId = useRecoilComponentValue(
+ selectedItemIdComponentState,
+ OBJECT_OPTIONS_DROPDOWN_ID,
+ );
+
+ const { openCreateViewDropdown } = useOpenCreateViewDropdown(recordIndexId);
+ const { closeDropdown } = useCloseDropdown();
+
+ const handleCreateCustomView = () => {
+ closeDropdown(OBJECT_OPTIONS_DROPDOWN_ID);
+
+ openCreateViewDropdown(currentView);
+ };
+
+ const { copyToClipboard } = useCopyToClipboard();
+
+ const { getIcon } = useIcons();
+ const MainIcon = getIcon(currentView?.icon);
+
+ return (
+
+
+
+
+
+
+
+ onContentChange('fields')}
+ >
+
+
+
+
+ {
+ const currentUrl = window.location.href;
+ copyToClipboard(currentUrl, t`Link copied to clipboard`);
+ }}
+ >
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuContent.tsx b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuContent.tsx
index 380e3a1f84f..e7445b52dfd 100644
--- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuContent.tsx
+++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuContent.tsx
@@ -1,208 +1,16 @@
-import { ObjectOptionsDropdownMenuViewName } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuViewName';
-import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId';
-import { useObjectOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsDropdown';
-import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
-import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
-import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
-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 { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
-import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
+import { ObjectOptionsDropdownCustomView } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownCustomView';
+import { ObjectOptionsDropdownDefaultView } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownDefaultView';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
-import { ViewType, viewTypeIconMapping } from '@/views/types/ViewType';
-import { useDeleteViewFromCurrentState } from '@/views/view-picker/hooks/useDeleteViewFromCurrentState';
-import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/states/viewPickerReferenceViewIdComponentState';
-import { useLingui } from '@lingui/react/macro';
-import { capitalize, isDefined } from 'twenty-shared/utils';
-import {
- AppTooltip,
- IconCopy,
- IconLayoutList,
- IconListDetails,
- IconTrash,
-} from 'twenty-ui/display';
-import { MenuItem } from 'twenty-ui/navigation';
-import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
+import { ViewKey } from '@/views/types/ViewKey';
export const ObjectOptionsDropdownMenuContent = () => {
- const { t } = useLingui();
- const { recordIndexId, objectMetadataItem, onContentChange, closeDropdown } =
- useObjectOptionsDropdown();
-
const { currentView } = useGetCurrentViewOnly();
- const recordGroupFieldMetadata = useRecoilComponentValue(
- recordGroupFieldMetadataComponentState,
- );
+ const isDefaultView = currentView?.key === ViewKey.Index;
- const isGroupByEnabled =
- (isDefined(currentView?.viewGroups) && currentView.viewGroups.length > 0) ||
- currentView?.key !== 'INDEX';
+ if (isDefaultView) {
+ return ;
+ }
- const { visibleBoardFields } = useObjectOptionsForBoard({
- objectNameSingular: objectMetadataItem.nameSingular,
- recordBoardId: recordIndexId,
- viewBarId: recordIndexId,
- });
-
- const { deleteViewFromCurrentState } = useDeleteViewFromCurrentState();
- const setViewPickerReferenceViewId = useSetRecoilComponentState(
- viewPickerReferenceViewIdComponentState,
- );
- const handleDelete = () => {
- if (!currentView?.id) {
- return;
- }
- setViewPickerReferenceViewId(currentView?.id);
- deleteViewFromCurrentState();
- closeDropdown();
- };
-
- const isDefaultView = currentView?.key === 'INDEX';
-
- const selectableItemIdArray = [
- 'Layout',
- 'Fields',
- ...(isDefaultView ? [] : ['Group']),
- 'Copy link to view',
- ...(isDefaultView ? [] : ['Delete view']),
- ];
-
- const selectedItemId = useRecoilComponentValue(
- selectedItemIdComponentState,
- OBJECT_OPTIONS_DROPDOWN_ID,
- );
-
- const { copyToClipboard } = useCopyToClipboard();
-
- return (
-
- {currentView && (
-
- )}
-
-
-
- onContentChange('layout')}
- >
-
-
-
-
- onContentChange('fields')}
- >
-
-
- {!isGroupByEnabled && (
-
- )}
-
-
-
- {
- const currentUrl = window.location.href;
- copyToClipboard(currentUrl, t`Link copied to clipboard`);
- }}
- >
-
-
- {currentView?.key === 'INDEX' && (
-
- )}
-
-
-
- );
+ return ;
};
diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuViewName.tsx b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuViewName.tsx
index e3cc4c19317..3e08c6f04d4 100644
--- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuViewName.tsx
+++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuViewName.tsx
@@ -1,5 +1,3 @@
-import { Key } from 'ts-key-enum';
-
import { useUpdateObjectViewOptions } from '@/object-record/object-options-dropdown/hooks/useUpdateObjectViewOptions';
import { IconPicker } from '@/ui/input/components/IconPicker';
import { TextInput } from '@/ui/input/components/TextInput';
@@ -16,7 +14,8 @@ import { viewPickerIsPersistingComponentState } from '@/views/view-picker/states
import { viewPickerSelectedIconComponentState } from '@/views/view-picker/states/viewPickerSelectedIconComponentState';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
-import { useEffect, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
+import { Key } from 'ts-key-enum';
import { OverflowingTextWithTooltip, useIcons } from 'twenty-ui/display';
import { useDebouncedCallback } from 'use-debounce';
@@ -43,6 +42,7 @@ const StyledMenuIconContainer = styled.div`
justify-content: center;
width: ${({ theme }) => theme.spacing(6)};
`;
+
const StyledMainText = styled.div`
color: ${({ theme }) => theme.font.color.primary};
flex-shrink: 0;
@@ -75,6 +75,8 @@ export const ObjectOptionsDropdownMenuViewName = ({
const { updateViewFromCurrentState } = useUpdateViewFromCurrentState();
const [viewName, setViewName] = useState(currentView?.name);
+ const inputRef = useRef(null);
+
useHotkeysOnFocusedElement({
keys: [Key.Enter],
callback: async () => {
@@ -102,23 +104,28 @@ export const ObjectOptionsDropdownMenuViewName = ({
setViewPickerSelectedIcon(currentView.icon);
}, [currentView.icon, setViewPickerSelectedIcon]);
+ useEffect(() => {
+ if (currentView?.key !== 'INDEX' && inputRef.current !== null) {
+ inputRef.current.focus();
+ }
+ }, [currentView?.key]);
+
const theme = useTheme();
const { getIcon } = useIcons();
const MainIcon = getIcon(currentView?.icon);
return (
<>
- {currentView?.key === 'INDEX' && (
+ {currentView?.key === 'INDEX' ? (
-
+
- )}
- {currentView?.key !== 'INDEX' && (
+ ) : (
{
+ const setViewPickerReferenceViewId = useSetRecoilComponentState(
+ viewPickerReferenceViewIdComponentState,
+ viewBardId,
+ );
+
+ const { setViewPickerMode } = useViewPickerMode(viewBardId);
+
+ const { openDropdown } = useOpenDropdown();
+
+ const openCreateViewDropdown = (referenceView: View | undefined) => {
+ if (isDefined(referenceView?.id)) {
+ setViewPickerReferenceViewId(referenceView.id);
+ setViewPickerMode('create-empty');
+ openDropdown({
+ dropdownComponentInstanceIdFromProps: VIEW_PICKER_DROPDOWN_ID,
+ });
+ }
+ };
+
+ return { openCreateViewDropdown };
+};
diff --git a/packages/twenty-front/src/modules/views/types/ViewKey.ts b/packages/twenty-front/src/modules/views/types/ViewKey.ts
index aff16196506..baf867929bf 100644
--- a/packages/twenty-front/src/modules/views/types/ViewKey.ts
+++ b/packages/twenty-front/src/modules/views/types/ViewKey.ts
@@ -1,3 +1,4 @@
export enum ViewKey {
Index = 'INDEX',
+ Custom = 'CUSTOM',
}
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 6ff7468c59d..a939f9f4b32 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
@@ -13,6 +13,7 @@ import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { useChangeView } from '@/views/hooks/useChangeView';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
+import { useOpenCreateViewDropdown } from '@/views/hooks/useOpenCreateViewDropown';
import { useUpdateView } from '@/views/hooks/useUpdateView';
import { ViewPickerOptionDropdown } from '@/views/view-picker/components/ViewPickerOptionDropdown';
import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId';
@@ -20,7 +21,6 @@ import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode';
import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/states/viewPickerReferenceViewIdComponentState';
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue } from 'recoil';
-import { isDefined } from 'twenty-shared/utils';
import { IconPlus } from 'twenty-ui/display';
import { MenuItem } from 'twenty-ui/navigation';
import { moveArrayItem } from '~/utils/array/moveArrayItem';
@@ -58,11 +58,10 @@ export const ViewPickerListContent = () => {
closeDropdown(VIEW_PICKER_DROPDOWN_ID);
};
+ const { openCreateViewDropdown } = useOpenCreateViewDropdown();
+
const handleAddViewButtonClick = () => {
- if (isDefined(currentView?.id)) {
- setViewPickerReferenceViewId(currentView.id);
- setViewPickerMode('create-empty');
- }
+ openCreateViewDropdown(currentView);
};
const handleEditViewButtonClick = (