[feature]:Improved Options Menu for Default View (#13427)
**Feature** - Improved Options Menu for Default View **Closes** - #13306 **Description** This PR fixes the feature request of Better Options in the Default View. Added the option of Create a Custom View in the Options, this helps user create custom views. **Working Demo** https://github.com/user-attachments/assets/32a3f264-c4a0-42aa-9257-56a407ea3c6e --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
co-authored by
Lucas Bordeau
parent
f72c3d73bf
commit
ff3f1d945c
+239
@@ -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 (
|
||||
<DropdownContent>
|
||||
<ObjectOptionsDropdownMenuViewName currentView={customViewData} />
|
||||
<DropdownMenuSeparator />
|
||||
<SelectableList
|
||||
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
|
||||
focusId={OBJECT_OPTIONS_DROPDOWN_ID}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
>
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<SelectableListItem
|
||||
itemId="Layout"
|
||||
onEnter={() => onContentChange('layout')}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === 'Layout'}
|
||||
onClick={() => onContentChange('layout')}
|
||||
LeftIcon={viewTypeIconMapping(
|
||||
customViewData?.type ?? ViewType.Table,
|
||||
)}
|
||||
text={t`Layout`}
|
||||
contextualText={`${capitalize(customViewData?.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')
|
||||
: onContentChange('recordGroupFields')
|
||||
}
|
||||
LeftIcon={IconLayoutList}
|
||||
text={t`Group`}
|
||||
contextualText={
|
||||
isDefaultView
|
||||
? t`Not available on Default View`
|
||||
: recordGroupFieldMetadata?.label
|
||||
}
|
||||
hasSubMenu
|
||||
disabled={isDefaultView}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</div>
|
||||
{isDefaultView && (
|
||||
<AppTooltip
|
||||
anchorSelect={`#group-by-menu-item`}
|
||||
content={t`Not available on Default View`}
|
||||
noArrow
|
||||
place="bottom"
|
||||
width="100%"
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<SelectableListItem
|
||||
itemId="Copy link to view"
|
||||
onEnter={() => {
|
||||
const currentUrl = window.location.href;
|
||||
navigator.clipboard.writeText(currentUrl);
|
||||
enqueueSuccessSnackBar({
|
||||
message: t`Link copied to clipboard`,
|
||||
options: {
|
||||
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);
|
||||
enqueueSuccessSnackBar({
|
||||
message: t`Link copied to clipboard`,
|
||||
options: {
|
||||
icon: <IconCopy size={theme.icon.size.md} />,
|
||||
duration: 2000,
|
||||
},
|
||||
});
|
||||
}}
|
||||
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={isDefaultView}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</div>
|
||||
{isDefaultView && (
|
||||
<AppTooltip
|
||||
anchorSelect={`#delete-view-menu-item`}
|
||||
content={t`Not available on Default View`}
|
||||
noArrow
|
||||
place="bottom"
|
||||
width="100%"
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</SelectableList>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
+128
@@ -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 (
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<MenuItem
|
||||
text={t`Default View`}
|
||||
LeftIcon={MainIcon}
|
||||
RightIcon={IconLock}
|
||||
disabled={true}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
<SelectableList
|
||||
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
|
||||
focusId={OBJECT_OPTIONS_DROPDOWN_ID}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
>
|
||||
<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>
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<SelectableListItem
|
||||
itemId="Copy link to view"
|
||||
onEnter={() => {
|
||||
const currentUrl = window.location.href;
|
||||
copyToClipboard(currentUrl, t`Link copied to clipboard`);
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === 'Copy link to view'}
|
||||
onClick={() => {
|
||||
const currentUrl = window.location.href;
|
||||
copyToClipboard(currentUrl, t`Link copied to clipboard`);
|
||||
}}
|
||||
LeftIcon={IconCopy}
|
||||
text={t`Copy link to view`}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
<SelectableListItem
|
||||
itemId="Create custom view"
|
||||
onEnter={handleCreateCustomView}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === 'Create custom view'}
|
||||
onClick={handleCreateCustomView}
|
||||
LeftIcon={IconLayout}
|
||||
text={t`Create custom view`}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</DropdownMenuItemsContainer>
|
||||
</SelectableList>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
+8
-200
@@ -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 <ObjectOptionsDropdownDefaultView />;
|
||||
}
|
||||
|
||||
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 (
|
||||
<DropdownContent>
|
||||
{currentView && (
|
||||
<ObjectOptionsDropdownMenuViewName currentView={currentView} />
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
<SelectableList
|
||||
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
|
||||
focusId={OBJECT_OPTIONS_DROPDOWN_ID}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
>
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<SelectableListItem
|
||||
itemId="Layout"
|
||||
onEnter={() => onContentChange('layout')}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === 'Layout'}
|
||||
onClick={() => onContentChange('layout')}
|
||||
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')
|
||||
: onContentChange('recordGroupFields')
|
||||
}
|
||||
LeftIcon={IconLayoutList}
|
||||
text={t`Group`}
|
||||
contextualText={
|
||||
isDefaultView
|
||||
? t`Not available on Default View`
|
||||
: recordGroupFieldMetadata?.label
|
||||
}
|
||||
hasSubMenu
|
||||
disabled={isDefaultView}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</div>
|
||||
{!isGroupByEnabled && (
|
||||
<AppTooltip
|
||||
anchorSelect={`#group-by-menu-item`}
|
||||
content={t`Not available on Default View`}
|
||||
noArrow
|
||||
place="bottom"
|
||||
width="100%"
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<SelectableListItem
|
||||
itemId="Copy link to view"
|
||||
onEnter={() => {
|
||||
const currentUrl = window.location.href;
|
||||
copyToClipboard(currentUrl, t`Link copied to clipboard`);
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === 'Copy link to view'}
|
||||
onClick={() => {
|
||||
const currentUrl = window.location.href;
|
||||
copyToClipboard(currentUrl, t`Link copied to clipboard`);
|
||||
}}
|
||||
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
|
||||
anchorSelect={`#delete-view-menu-item`}
|
||||
content={t`Not available on Default View`}
|
||||
noArrow
|
||||
place="bottom"
|
||||
width="100%"
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</SelectableList>
|
||||
</DropdownContent>
|
||||
);
|
||||
return <ObjectOptionsDropdownCustomView />;
|
||||
};
|
||||
|
||||
+14
-7
@@ -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<HTMLInputElement>(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' ? (
|
||||
<StyledMenuTitleContainer>
|
||||
<StyledMenuIconContainer>
|
||||
<MainIcon size={theme.icon.size.md} stroke={theme.icon.stroke.sm} />
|
||||
</StyledMenuIconContainer>
|
||||
<StyledMainText>
|
||||
<OverflowingTextWithTooltip text={currentView?.name} />
|
||||
<OverflowingTextWithTooltip text={currentView.name} />
|
||||
</StyledMainText>
|
||||
</StyledMenuTitleContainer>
|
||||
)}
|
||||
{currentView?.key !== 'INDEX' && (
|
||||
) : (
|
||||
<DropdownMenuItemsContainer>
|
||||
<StyledDropdownMenuIconAndNameContainer>
|
||||
<IconPicker
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import { type View } from '@/views/types/View';
|
||||
import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId';
|
||||
import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode';
|
||||
import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/states/viewPickerReferenceViewIdComponentState';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const useOpenCreateViewDropdown = (viewBardId?: string) => {
|
||||
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 };
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
export enum ViewKey {
|
||||
Index = 'INDEX',
|
||||
Custom = 'CUSTOM',
|
||||
}
|
||||
|
||||
+4
-5
@@ -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 = (
|
||||
|
||||
Reference in New Issue
Block a user