Navbar customization improvements (#17863)
- Add folder icon editing support - And other navbar customization fixes/improvements. --------- Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> Co-authored-by: Devessier <baptiste@devessier.fr> Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
@@ -5,7 +5,7 @@ import {
|
||||
useNavigate,
|
||||
useParams,
|
||||
} from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||
|
||||
import {
|
||||
setSessionId,
|
||||
@@ -17,6 +17,8 @@ import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCapt
|
||||
import { isCaptchaScriptLoadedState } from '@/captcha/states/isCaptchaScriptLoadedState';
|
||||
import { isCaptchaRequiredForPath } from '@/captcha/utils/isCaptchaRequiredForPath';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
|
||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
||||
@@ -97,6 +99,20 @@ export const PageChangeEffect = () => {
|
||||
|
||||
const { closeCommandMenu } = useCommandMenu();
|
||||
|
||||
const closeCommandMenuUnlessOnEditPage = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
() => {
|
||||
const currentPage = snapshot
|
||||
.getLoadable(commandMenuPageState)
|
||||
.getValue();
|
||||
if (currentPage === CommandMenuPages.NavigationMenuItemEdit) {
|
||||
return;
|
||||
}
|
||||
closeCommandMenu();
|
||||
},
|
||||
[closeCommandMenu],
|
||||
);
|
||||
|
||||
const { resetFocusStackToFocusItem } = useResetFocusStackToFocusItem();
|
||||
|
||||
const { resetFocusStackToRecordIndex } = useResetFocusStackToRecordIndex();
|
||||
@@ -104,8 +120,8 @@ export const PageChangeEffect = () => {
|
||||
const { openNewRecordTitleCell } = useOpenNewRecordTitleCell();
|
||||
|
||||
useEffect(() => {
|
||||
closeCommandMenu();
|
||||
}, [location.pathname, closeCommandMenu]);
|
||||
closeCommandMenuUnlessOnEditPage();
|
||||
}, [location.pathname, closeCommandMenuUnlessOnEditPage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!previousLocation || previousLocation !== location.pathname) {
|
||||
|
||||
+37
-15
@@ -1,24 +1,32 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { IconFolder } from 'twenty-ui/display';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
|
||||
import { CommandMenuPageInfoLayout } from '@/command-menu/components/CommandMenuPageInfoLayout';
|
||||
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
|
||||
import { commandMenuShouldFocusTitleInputComponentState } from '@/command-menu/states/commandMenuShouldFocusTitleInputComponentState';
|
||||
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
|
||||
import { StyledNavigationMenuItemIconContainer } from '@/navigation-menu-item/components/NavigationMenuItemIconContainer';
|
||||
import { useUpdateFolderNameInDraft } from '@/navigation-menu-item/hooks/useUpdateFolderNameInDraft';
|
||||
import { FOLDER_ICON_DEFAULT } from '@/navigation-menu-item/constants/FolderIconDefault';
|
||||
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
|
||||
import { useUpdateFolderInDraft } from '@/navigation-menu-item/hooks/useUpdateFolderInDraft';
|
||||
import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems';
|
||||
import { selectedNavigationMenuItemInEditModeStateV2 } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeStateV2';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors';
|
||||
import { IconPicker } from '@/ui/input/components/IconPicker';
|
||||
import { TitleInput } from '@/ui/input/components/TitleInput';
|
||||
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
|
||||
|
||||
const StyledClickableIconWrapper = styled.div`
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export const CommandMenuFolderInfo = () => {
|
||||
const theme = useTheme();
|
||||
const { t } = useLingui();
|
||||
const { getIcon } = useIcons();
|
||||
const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
|
||||
const [shouldFocusTitleInput, setShouldFocusTitleInput] =
|
||||
useRecoilComponentState(
|
||||
@@ -29,7 +37,7 @@ export const CommandMenuFolderInfo = () => {
|
||||
selectedNavigationMenuItemInEditModeStateV2,
|
||||
);
|
||||
const items = useWorkspaceSectionItems();
|
||||
const { updateFolderNameInDraft } = useUpdateFolderNameInDraft();
|
||||
const { updateFolderInDraft } = useUpdateFolderInDraft();
|
||||
|
||||
const defaultLabel = t`New folder`;
|
||||
const placeholder = t`Folder name`;
|
||||
@@ -48,7 +56,7 @@ export const CommandMenuFolderInfo = () => {
|
||||
const itemName = selectedItem.name ?? defaultLabel;
|
||||
|
||||
const handleChange = (text: string) => {
|
||||
updateFolderNameInDraft(itemId, text);
|
||||
updateFolderInDraft(itemId, { name: text });
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
@@ -56,22 +64,36 @@ export const CommandMenuFolderInfo = () => {
|
||||
const finalName = trimmed.length > 0 ? trimmed : defaultLabel;
|
||||
|
||||
if (finalName !== itemName) {
|
||||
updateFolderNameInDraft(itemId, finalName);
|
||||
updateFolderInDraft(itemId, { name: finalName });
|
||||
}
|
||||
};
|
||||
|
||||
const selectedIconKey = selectedItem.icon ?? FOLDER_ICON_DEFAULT;
|
||||
const FolderIconComponent = getIcon(selectedIconKey);
|
||||
|
||||
return (
|
||||
<CommandMenuPageInfoLayout
|
||||
icon={
|
||||
<StyledNavigationMenuItemIconContainer
|
||||
$backgroundColor={getNavigationMenuItemIconColors(theme).folder}
|
||||
>
|
||||
<IconFolder
|
||||
size={theme.spacing(3.5)}
|
||||
color={theme.grayScale.gray1}
|
||||
stroke={theme.icon.stroke.md}
|
||||
/>
|
||||
</StyledNavigationMenuItemIconContainer>
|
||||
<IconPicker
|
||||
dropdownId="command-menu-folder-icon-picker"
|
||||
selectedIconKey={selectedIconKey}
|
||||
onChange={({ iconKey }) =>
|
||||
updateFolderInDraft(itemId, { icon: iconKey })
|
||||
}
|
||||
clickableComponent={
|
||||
<StyledClickableIconWrapper>
|
||||
<StyledNavigationMenuItemIconContainer
|
||||
$backgroundColor={getNavigationMenuItemIconColors(theme).folder}
|
||||
>
|
||||
<FolderIconComponent
|
||||
size={theme.spacing(3.5)}
|
||||
color={theme.grayScale.gray1}
|
||||
stroke={theme.icon.stroke.md}
|
||||
/>
|
||||
</StyledNavigationMenuItemIconContainer>
|
||||
</StyledClickableIconWrapper>
|
||||
}
|
||||
/>
|
||||
}
|
||||
title={
|
||||
<TitleInput
|
||||
|
||||
+10
-10
@@ -3,10 +3,9 @@ import { OverflowingTextWithTooltip } from 'twenty-ui/display';
|
||||
|
||||
import { CommandMenuPageInfoLayout } from '@/command-menu/components/CommandMenuPageInfoLayout';
|
||||
import { NavigationMenuItemIcon } from '@/navigation-menu-item/components/NavigationMenuItemIcon';
|
||||
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
|
||||
import { useSelectedNavigationMenuItemEditItem } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem';
|
||||
import { useSelectedNavigationMenuItemEditItemLabel } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemLabel';
|
||||
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
|
||||
import { ViewKey } from '@/views/types/ViewKey';
|
||||
|
||||
export const CommandMenuObjectViewRecordInfo = () => {
|
||||
const { t } = useLingui();
|
||||
@@ -22,19 +21,20 @@ export const CommandMenuObjectViewRecordInfo = () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isViewOrRecord =
|
||||
processedItem.itemType === NavigationMenuItemType.VIEW ||
|
||||
processedItem.itemType === NavigationMenuItemType.RECORD;
|
||||
const isViewOrRecord = [
|
||||
NavigationMenuItemType.VIEW,
|
||||
NavigationMenuItemType.RECORD,
|
||||
].includes(processedItem.itemType);
|
||||
|
||||
if (!isViewOrRecord) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const label =
|
||||
processedItem.itemType === NavigationMenuItemType.RECORD
|
||||
const label = isViewOrRecord
|
||||
? processedItem.itemType === NavigationMenuItemType.RECORD
|
||||
? t`record`
|
||||
: processedItem.viewKey === ViewKey.Index
|
||||
? t`object`
|
||||
: t`view`;
|
||||
: t`view`
|
||||
: t`object`;
|
||||
|
||||
return (
|
||||
<CommandMenuPageInfoLayout
|
||||
|
||||
+16
-1
@@ -10,6 +10,7 @@ import { CommandMenuSubViewWithSearch } from '@/command-menu/components/CommandM
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { useFilteredPickerItems } from '@/command-menu/hooks/useFilteredPickerItems';
|
||||
import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems';
|
||||
import { ObjectIconWithViewOverlay } from '@/navigation-menu-item/components/ObjectIconWithViewOverlay';
|
||||
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
|
||||
import { useAddViewToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddViewToNavigationMenuDraft';
|
||||
import { useNavigationMenuObjectMetadataFromDraft } from '@/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft';
|
||||
@@ -110,7 +111,21 @@ export const CommandMenuNewSidebarItemViewPickerSubView = ({
|
||||
onEnter={() => handleSelectView(view)}
|
||||
>
|
||||
<CommandMenuItemWithAddToNavigationDrag
|
||||
icon={getIcon(view.icon)}
|
||||
customIconContent={
|
||||
selectedObjectMetadataItem ? (
|
||||
<ObjectIconWithViewOverlay
|
||||
ObjectIcon={getIcon(
|
||||
selectedObjectMetadataItem.icon,
|
||||
)}
|
||||
ViewIcon={getIcon(view.icon)}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
icon={
|
||||
selectedObjectMetadataItem
|
||||
? undefined
|
||||
: getIcon(view.icon)
|
||||
}
|
||||
label={view.name}
|
||||
id={view.id}
|
||||
onClick={() => handleSelectView(view)}
|
||||
|
||||
+9
-1
@@ -72,14 +72,22 @@ export const AddToNavigationDragHandle = ({
|
||||
const iconSize = theme.icon.size.md;
|
||||
const iconStroke = theme.icon.stroke.sm;
|
||||
|
||||
const showCustomContentWithoutWrapper = isDefined(customIconContent);
|
||||
|
||||
return (
|
||||
<StyledIconSlot $hasFixedSize={payloadHasBackgroundColor}>
|
||||
<StyledIconSlot
|
||||
$hasFixedSize={
|
||||
payloadHasBackgroundColor || showCustomContentWithoutWrapper
|
||||
}
|
||||
>
|
||||
{isHovered ? (
|
||||
<IconGripVertical
|
||||
size={iconSize}
|
||||
stroke={iconStroke}
|
||||
color={theme.font.color.tertiary}
|
||||
/>
|
||||
) : showCustomContentWithoutWrapper ? (
|
||||
customIconContent
|
||||
) : hasBackgroundColor ? (
|
||||
<StyledNavigationMenuItemIconContainer
|
||||
$backgroundColor={iconBackgroundColor}
|
||||
|
||||
+17
-8
@@ -1,20 +1,22 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import { IconCheck, useIcons } from 'twenty-ui/display';
|
||||
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
|
||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||
import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/useNavigationMenuItemsDraftState';
|
||||
import { useSaveNavigationMenuItemsDraft } from '@/navigation-menu-item/hooks/useSaveNavigationMenuItemsDraft';
|
||||
import { isNavigationMenuInEditModeStateV2 } from '@/navigation-menu-item/states/isNavigationMenuInEditModeStateV2';
|
||||
import { navigationMenuItemsDraftStateV2 } from '@/navigation-menu-item/states/navigationMenuItemsDraftStateV2';
|
||||
import { selectedNavigationMenuItemInEditModeStateV2 } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeStateV2';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
|
||||
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { IconCheck, useIcons } from 'twenty-ui/display';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
@@ -40,6 +42,7 @@ export const NavigationMenuEditModeBar = () => {
|
||||
const { getIcon } = useIcons();
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const { closeCommandMenu } = useCommandMenu();
|
||||
const commandMenuPage = useRecoilValue(commandMenuPageState);
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
const setNavigationMenuItemsDraft = useSetRecoilStateV2(
|
||||
navigationMenuItemsDraftStateV2,
|
||||
@@ -57,6 +60,12 @@ export const NavigationMenuEditModeBar = () => {
|
||||
setNavigationMenuItemsDraft(null);
|
||||
setSelectedNavigationMenuItemInEditMode(null);
|
||||
setIsNavigationMenuInEditMode(false);
|
||||
const isNavItemPageOpen =
|
||||
commandMenuPage === CommandMenuPages.NavigationMenuAddItem ||
|
||||
commandMenuPage === CommandMenuPages.NavigationMenuItemEdit;
|
||||
if (isNavItemPageOpen) {
|
||||
closeCommandMenu();
|
||||
}
|
||||
};
|
||||
|
||||
const isNavigationMenuInEditMode = useRecoilValueV2(
|
||||
|
||||
+52
-15
@@ -1,12 +1,18 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Avatar, useIcons } from 'twenty-ui/display';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
import { StyledNavigationMenuItemIconContainer } from '@/navigation-menu-item/components/NavigationMenuItemIconContainer';
|
||||
import { ObjectIconWithViewOverlay } from '@/navigation-menu-item/components/ObjectIconWithViewOverlay';
|
||||
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
|
||||
import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors';
|
||||
import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems';
|
||||
import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandardObjectIcon';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { ViewKey } from '@/views/types/ViewKey';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
|
||||
export const NavigationMenuItemIcon = ({
|
||||
navigationMenuItem,
|
||||
@@ -15,30 +21,61 @@ export const NavigationMenuItemIcon = ({
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const { getIcon } = useIcons();
|
||||
const isNavigationMenuItemEditingEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED,
|
||||
);
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
const { Icon: StandardIcon, IconColor } = useGetStandardObjectIcon(
|
||||
navigationMenuItem.objectNameSingular || '',
|
||||
);
|
||||
|
||||
const isRecord =
|
||||
navigationMenuItem.itemType === NavigationMenuItemType.RECORD;
|
||||
const isLink = navigationMenuItem.itemType === NavigationMenuItemType.LINK;
|
||||
const isObjectIndexView =
|
||||
navigationMenuItem.itemType === NavigationMenuItemType.VIEW &&
|
||||
navigationMenuItem.viewKey === ViewKey.Index;
|
||||
const isViewWithOverlay =
|
||||
navigationMenuItem.itemType === NavigationMenuItemType.VIEW &&
|
||||
!isObjectIndexView;
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.nameSingular === navigationMenuItem.objectNameSingular,
|
||||
);
|
||||
const ObjectIconForView =
|
||||
objectMetadataItem?.icon != null
|
||||
? getIcon(objectMetadataItem.icon)
|
||||
: StandardIcon;
|
||||
|
||||
if (
|
||||
isViewWithOverlay &&
|
||||
isDefined(ObjectIconForView) &&
|
||||
isDefined(navigationMenuItem.Icon)
|
||||
) {
|
||||
const ViewIcon = getIcon(navigationMenuItem.Icon);
|
||||
return (
|
||||
<ObjectIconWithViewOverlay
|
||||
ObjectIcon={ObjectIconForView}
|
||||
ViewIcon={ViewIcon}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const IconToUse =
|
||||
StandardIcon ||
|
||||
(navigationMenuItem.Icon ? getIcon(navigationMenuItem.Icon) : undefined);
|
||||
|
||||
const placeholderColorSeed = navigationMenuItem.targetRecordId ?? undefined;
|
||||
|
||||
const isRecord =
|
||||
navigationMenuItem.itemType === NavigationMenuItemType.RECORD;
|
||||
const isLink = navigationMenuItem.itemType === NavigationMenuItemType.LINK;
|
||||
const iconColors = getNavigationMenuItemIconColors(theme);
|
||||
const isObjectIndexView =
|
||||
navigationMenuItem.itemType === NavigationMenuItemType.VIEW &&
|
||||
navigationMenuItem.viewKey === ViewKey.Index;
|
||||
const iconBackgroundColor = isRecord
|
||||
? undefined
|
||||
: isLink
|
||||
? iconColors.link
|
||||
: navigationMenuItem.itemType === NavigationMenuItemType.VIEW &&
|
||||
!isObjectIndexView
|
||||
? iconColors.view
|
||||
: iconColors.object;
|
||||
const iconBackgroundColor = isNavigationMenuItemEditingEnabled
|
||||
? isRecord
|
||||
? undefined
|
||||
: isLink
|
||||
? iconColors.link
|
||||
: isViewWithOverlay
|
||||
? iconColors.view
|
||||
: iconColors.object
|
||||
: undefined;
|
||||
|
||||
const iconColorToUse = iconBackgroundColor
|
||||
? theme.grayScale.gray1
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import type { IconComponent } from 'twenty-ui/display';
|
||||
|
||||
import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors';
|
||||
|
||||
const StyledCompositeContainer = styled.div`
|
||||
align-items: center;
|
||||
border-radius: ${({ theme }) => theme.border.radius.xs};
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
height: ${({ theme }) => theme.spacing(4.5)};
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
width: ${({ theme }) => theme.spacing(4.5)};
|
||||
`;
|
||||
|
||||
const StyledObjectIconWrapper = styled.div<{ $backgroundColor: string }>`
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: ${({ theme }) => theme.border.radius.xs};
|
||||
background-color: ${({ $backgroundColor }) => $backgroundColor};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const StyledViewOverlay = styled.div<{ $backgroundColor: string }>`
|
||||
align-items: center;
|
||||
background-color: ${({ $backgroundColor }) => $backgroundColor};
|
||||
border: 1px solid ${({ theme }) => theme.background.primary};
|
||||
border-radius: ${({ theme }) => theme.border.radius.xs};
|
||||
bottom: -7px;
|
||||
display: flex;
|
||||
height: ${({ theme }) => theme.spacing(3.5)};
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
right: -7px;
|
||||
width: ${({ theme }) => theme.spacing(3.5)};
|
||||
`;
|
||||
|
||||
export type ObjectIconWithViewOverlayProps = {
|
||||
ObjectIcon: IconComponent;
|
||||
ViewIcon: IconComponent;
|
||||
};
|
||||
|
||||
export const ObjectIconWithViewOverlay = ({
|
||||
ObjectIcon,
|
||||
ViewIcon,
|
||||
}: ObjectIconWithViewOverlayProps) => {
|
||||
const theme = useTheme();
|
||||
const iconColors = getNavigationMenuItemIconColors(theme);
|
||||
|
||||
return (
|
||||
<StyledCompositeContainer>
|
||||
<StyledObjectIconWrapper $backgroundColor={iconColors.object}>
|
||||
<ObjectIcon
|
||||
size={theme.spacing(3.5)}
|
||||
stroke={theme.icon.stroke.md}
|
||||
color={theme.grayScale.gray1}
|
||||
/>
|
||||
</StyledObjectIconWrapper>
|
||||
<StyledViewOverlay $backgroundColor={iconColors.view}>
|
||||
<ViewIcon
|
||||
size={theme.spacing(2)}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
color={theme.grayScale.gray1}
|
||||
/>
|
||||
</StyledViewOverlay>
|
||||
</StyledCompositeContainer>
|
||||
);
|
||||
};
|
||||
+16
-14
@@ -1,39 +1,36 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import {
|
||||
IconFolder,
|
||||
IconLink,
|
||||
IconPlus,
|
||||
IconTool,
|
||||
useIcons,
|
||||
} from 'twenty-ui/display';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { IconLink, IconPlus, IconTool, useIcons } from 'twenty-ui/display';
|
||||
import { LightIconButton } from 'twenty-ui/input';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
|
||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||
import { FOLDER_ICON_DEFAULT } from '@/navigation-menu-item/constants/FolderIconDefault';
|
||||
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
|
||||
import { useOpenNavigationMenuItemInCommandMenu } from '@/navigation-menu-item/hooks/useOpenNavigationMenuItemInCommandMenu';
|
||||
import {
|
||||
type NavigationMenuItemClickParams,
|
||||
useWorkspaceSectionItems,
|
||||
} from '@/navigation-menu-item/hooks/useWorkspaceSectionItems';
|
||||
import { isNavigationMenuInEditModeStateV2 } from '@/navigation-menu-item/states/isNavigationMenuInEditModeStateV2';
|
||||
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
|
||||
import { navigationMenuItemsDraftStateV2 } from '@/navigation-menu-item/states/navigationMenuItemsDraftStateV2';
|
||||
import { filterWorkspaceNavigationMenuItems } from '@/navigation-menu-item/utils/filterWorkspaceNavigationMenuItems';
|
||||
import { openNavigationMenuItemFolderIdsStateV2 } from '@/navigation-menu-item/states/openNavigationMenuItemFolderIdsStateV2';
|
||||
import { selectedNavigationMenuItemInEditModeStateV2 } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeStateV2';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2';
|
||||
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
|
||||
import { filterWorkspaceNavigationMenuItems } from '@/navigation-menu-item/utils/filterWorkspaceNavigationMenuItems';
|
||||
import { NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader';
|
||||
import { NavigationDrawerSectionForWorkspaceItems } from '@/object-metadata/components/NavigationDrawerSectionForWorkspaceItems';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
|
||||
import { prefetchNavigationMenuItemsState } from '@/prefetch/states/prefetchNavigationMenuItemsState';
|
||||
import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const StyledRightIconsContainer = styled.div`
|
||||
@@ -74,6 +71,7 @@ export const WorkspaceNavigationMenuItems = () => {
|
||||
const setOpenNavigationMenuItemFolderIds = useSetRecoilStateV2(
|
||||
openNavigationMenuItemFolderIdsStateV2,
|
||||
);
|
||||
const navigate = useNavigate();
|
||||
const { navigateCommandMenu } = useNavigateCommandMenu();
|
||||
const { openNavigationMenuItemInCommandMenu } =
|
||||
useOpenNavigationMenuItemInCommandMenu();
|
||||
@@ -101,7 +99,7 @@ export const WorkspaceNavigationMenuItems = () => {
|
||||
);
|
||||
openNavigationMenuItemInCommandMenu({
|
||||
pageTitle: t`Edit folder`,
|
||||
pageIcon: IconFolder,
|
||||
pageIcon: getIcon(item.icon ?? item.Icon ?? FOLDER_ICON_DEFAULT),
|
||||
});
|
||||
} else if (item.itemType === NavigationMenuItemType.LINK) {
|
||||
openNavigationMenuItemInCommandMenu({
|
||||
@@ -113,6 +111,10 @@ export const WorkspaceNavigationMenuItems = () => {
|
||||
pageTitle: objectMetadataItem.labelPlural,
|
||||
pageIcon: getIcon(objectMetadataItem.icon),
|
||||
});
|
||||
|
||||
if (isNonEmptyString(item.link)) {
|
||||
navigate(item.link);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+35
-5
@@ -8,19 +8,19 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { useIsDropDisabledForSection } from '@/navigation-menu-item/hooks/useIsDropDisabledForSection';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { IconFolder, IconFolderOpen } from 'twenty-ui/display';
|
||||
import { IconChevronDown, IconChevronRight, useIcons } from 'twenty-ui/display';
|
||||
import { AnimatedExpandableContainer } from 'twenty-ui/layout';
|
||||
import { useIsMobile } from 'twenty-ui/utilities';
|
||||
|
||||
import { NavigationMenuItemDroppable } from '@/navigation-menu-item/components/NavigationMenuItemDroppable';
|
||||
import { NavigationMenuItemIcon } from '@/navigation-menu-item/components/NavigationMenuItemIcon';
|
||||
import { WorkspaceNavigationMenuItemFolderDragClone } from '@/navigation-menu-item/components/WorkspaceNavigationMenuItemFolderDragClone';
|
||||
import { FOLDER_ICON_DEFAULT } from '@/navigation-menu-item/constants/FolderIconDefault';
|
||||
import { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds';
|
||||
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
|
||||
import { NavigationMenuItemDragContext } from '@/navigation-menu-item/contexts/NavigationMenuItemDragContext';
|
||||
import { type NavigationMenuItemClickParams } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems';
|
||||
import { openNavigationMenuItemFolderIdsStateV2 } from '@/navigation-menu-item/states/openNavigationMenuItemFolderIdsStateV2';
|
||||
import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2';
|
||||
import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors';
|
||||
import { getNavigationMenuItemSecondaryLabel } from '@/navigation-menu-item/utils/getNavigationMenuItemSecondaryLabel';
|
||||
import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem';
|
||||
@@ -32,11 +32,14 @@ import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/componen
|
||||
import { NavigationDrawerItemsCollapsableContainer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemsCollapsableContainer';
|
||||
import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem';
|
||||
import { currentNavigationMenuItemFolderIdStateV2 } from '@/ui/navigation/navigation-drawer/states/currentNavigationMenuItemFolderIdStateV2';
|
||||
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
|
||||
import { getNavigationSubItemLeftAdornment } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemLeftAdornment';
|
||||
import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2';
|
||||
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
|
||||
import { coreViewsState } from '@/views/states/coreViewState';
|
||||
import { ViewKey } from '@/views/types/ViewKey';
|
||||
import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledFolderContainer = styled.div<{ $isSelectedInEditMode: boolean }>`
|
||||
border: ${({ theme, $isSelectedInEditMode }) =>
|
||||
@@ -49,6 +52,9 @@ const StyledFolderContainer = styled.div<{ $isSelectedInEditMode: boolean }>`
|
||||
const StyledFolderDroppableContent = styled.div<{
|
||||
$compact: boolean;
|
||||
}>`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.betweenSiblingsGap};
|
||||
padding-bottom: ${({ theme, $compact }) => ($compact ? 0 : theme.spacing(2))};
|
||||
`;
|
||||
|
||||
@@ -61,6 +67,7 @@ const StyledFolderExpandableWrapper = styled.div`
|
||||
type WorkspaceNavigationMenuItemsFolderProps = {
|
||||
folderId: string;
|
||||
folderName: string;
|
||||
folderIconKey?: string | null;
|
||||
navigationMenuItems: ProcessedNavigationMenuItem[];
|
||||
isGroup: boolean;
|
||||
isEditMode?: boolean;
|
||||
@@ -74,6 +81,7 @@ type WorkspaceNavigationMenuItemsFolderProps = {
|
||||
export const WorkspaceNavigationMenuItemsFolder = ({
|
||||
folderId,
|
||||
folderName,
|
||||
folderIconKey,
|
||||
navigationMenuItems,
|
||||
isGroup,
|
||||
isEditMode = false,
|
||||
@@ -84,7 +92,12 @@ export const WorkspaceNavigationMenuItemsFolder = ({
|
||||
isDragging = false,
|
||||
}: WorkspaceNavigationMenuItemsFolderProps) => {
|
||||
const theme = useTheme();
|
||||
const { getIcon } = useIcons();
|
||||
const isNavigationMenuItemEditingEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED,
|
||||
);
|
||||
const iconColors = getNavigationMenuItemIconColors(theme);
|
||||
const FolderIcon = getIcon(folderIconKey ?? FOLDER_ICON_DEFAULT);
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
const coreViews = useRecoilValue(coreViewsState);
|
||||
const views = coreViews.map(convertCoreViewToView);
|
||||
@@ -149,13 +162,30 @@ export const WorkspaceNavigationMenuItemsFolder = ({
|
||||
>
|
||||
<NavigationDrawerItem
|
||||
label={folderName}
|
||||
Icon={isOpen ? IconFolderOpen : IconFolder}
|
||||
iconBackgroundColor={iconColors.folder}
|
||||
Icon={FolderIcon}
|
||||
iconBackgroundColor={
|
||||
isNavigationMenuItemEditingEnabled ? iconColors.folder : undefined
|
||||
}
|
||||
onClick={handleClick}
|
||||
className="navigation-drawer-item"
|
||||
triggerEvent="CLICK"
|
||||
preventCollapseOnMobile={isMobile}
|
||||
isDragging={isDragging}
|
||||
rightOptions={
|
||||
isOpen ? (
|
||||
<IconChevronDown
|
||||
size={theme.icon.size.sm}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
color={theme.font.color.tertiary}
|
||||
/>
|
||||
) : (
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.sm}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
color={theme.font.color.tertiary}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</NavigationMenuItemDroppable>
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const FOLDER_ICON_DEFAULT = 'IconFolder';
|
||||
+7
@@ -1,10 +1,16 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export type AddToNavigationFallbackDestination = {
|
||||
droppableId: string;
|
||||
index: number;
|
||||
} | null;
|
||||
|
||||
type NavigationDropTargetContextType = {
|
||||
activeDropTargetId: string | null;
|
||||
setActiveDropTargetId: (id: string | null) => void;
|
||||
forbiddenDropTargetId: string | null;
|
||||
setForbiddenDropTargetId: (id: string | null) => void;
|
||||
addToNavigationFallbackDestination: AddToNavigationFallbackDestination;
|
||||
};
|
||||
|
||||
export const NavigationDropTargetContext =
|
||||
@@ -13,4 +19,5 @@ export const NavigationDropTargetContext =
|
||||
setActiveDropTargetId: () => {},
|
||||
forbiddenDropTargetId: null,
|
||||
setForbiddenDropTargetId: () => {},
|
||||
addToNavigationFallbackDestination: null,
|
||||
});
|
||||
|
||||
+1
@@ -10,6 +10,7 @@ export const NAVIGATION_MENU_ITEM_FRAGMENT = gql`
|
||||
folderId
|
||||
name
|
||||
link
|
||||
icon
|
||||
position
|
||||
applicationId
|
||||
createdAt
|
||||
|
||||
+15
-11
@@ -1,7 +1,10 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { useCreateNavigationMenuItemMutation } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
type CreateNavigationMenuItemInput,
|
||||
useCreateNavigationMenuItemMutation,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
import { useDeleteNavigationMenuItem } from '@/navigation-menu-item/hooks/useDeleteNavigationMenuItem';
|
||||
import { useUpdateNavigationMenuItem } from '@/navigation-menu-item/hooks/useUpdateNavigationMenuItem';
|
||||
@@ -81,20 +84,13 @@ export const useSaveNavigationMenuItemsDraft = () => {
|
||||
];
|
||||
|
||||
for (const draftItem of idsToCreateIncludingRecreated) {
|
||||
const input: {
|
||||
position: number;
|
||||
folderId?: string | null;
|
||||
name?: string;
|
||||
link?: string;
|
||||
viewId?: string;
|
||||
targetObjectMetadataId?: string;
|
||||
targetRecordId?: string;
|
||||
} = {
|
||||
const input: CreateNavigationMenuItemInput = {
|
||||
position: Math.max(0, Math.round(draftItem.position)),
|
||||
};
|
||||
|
||||
if (isNavigationMenuItemFolder(draftItem)) {
|
||||
input.name = draftItem.name ?? undefined;
|
||||
input.icon = draftItem.icon ?? null;
|
||||
} else if (isNavigationMenuItemLink(draftItem)) {
|
||||
input.name = draftItem.name ?? 'Link';
|
||||
const linkUrl = (draftItem.link ?? '').trim();
|
||||
@@ -138,12 +134,16 @@ export const useSaveNavigationMenuItemsDraft = () => {
|
||||
const linkChanged =
|
||||
isNavigationMenuItemLink(draftItem) &&
|
||||
(original.link ?? null) !== (draftItem.link ?? null);
|
||||
const iconChanged =
|
||||
isNavigationMenuItemFolder(draftItem) &&
|
||||
(original.icon ?? null) !== (draftItem.icon ?? null);
|
||||
|
||||
if (
|
||||
positionChanged ||
|
||||
folderIdChanged ||
|
||||
nameChanged ||
|
||||
linkChanged
|
||||
linkChanged ||
|
||||
iconChanged
|
||||
) {
|
||||
const updateInput: {
|
||||
id: string;
|
||||
@@ -151,6 +151,7 @@ export const useSaveNavigationMenuItemsDraft = () => {
|
||||
folderId?: string | null;
|
||||
name?: string;
|
||||
link?: string | null;
|
||||
icon?: string | null;
|
||||
} = { id: draftItem.id };
|
||||
|
||||
if (positionChanged) {
|
||||
@@ -177,6 +178,9 @@ export const useSaveNavigationMenuItemsDraft = () => {
|
||||
: `https://${linkUrl}`
|
||||
: null;
|
||||
}
|
||||
if (iconChanged && isNavigationMenuItemFolder(draftItem)) {
|
||||
updateInput.icon = draftItem.icon ?? null;
|
||||
}
|
||||
|
||||
await updateNavigationMenuItem(updateInput);
|
||||
}
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import { navigationMenuItemsDraftStateV2 } from '@/navigation-menu-item/states/navigationMenuItemsDraftStateV2';
|
||||
import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavigationMenuItemFolder';
|
||||
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
|
||||
|
||||
export type UpdateFolderInDraftUpdates = {
|
||||
name?: string;
|
||||
icon?: string;
|
||||
};
|
||||
|
||||
export const useUpdateFolderInDraft = () => {
|
||||
const setNavigationMenuItemsDraft = useSetRecoilStateV2(
|
||||
navigationMenuItemsDraftStateV2,
|
||||
);
|
||||
|
||||
const updateFolderInDraft = (
|
||||
folderId: string,
|
||||
updates: UpdateFolderInDraftUpdates,
|
||||
) => {
|
||||
setNavigationMenuItemsDraft((draft) => {
|
||||
if (!draft) return draft;
|
||||
|
||||
return draft.map((item) => {
|
||||
if (!isNavigationMenuItemFolder(item) || item.id !== folderId) {
|
||||
return item;
|
||||
}
|
||||
return { ...item, ...updates };
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return { updateFolderInDraft };
|
||||
};
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import { navigationMenuItemsDraftStateV2 } from '@/navigation-menu-item/states/navigationMenuItemsDraftStateV2';
|
||||
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
|
||||
|
||||
export type UpdateNavigationMenuItemInDraftUpdates = {
|
||||
color?: string;
|
||||
name?: string;
|
||||
icon?: string;
|
||||
link?: string;
|
||||
};
|
||||
|
||||
export const useUpdateNavigationMenuItemInDraft = () => {
|
||||
const setNavigationMenuItemsDraft = useSetRecoilStateV2(
|
||||
navigationMenuItemsDraftStateV2,
|
||||
);
|
||||
|
||||
const updateNavigationMenuItemInDraft = (
|
||||
navigationMenuItemId: string,
|
||||
updates: UpdateNavigationMenuItemInDraftUpdates,
|
||||
) => {
|
||||
setNavigationMenuItemsDraft((draft) => {
|
||||
if (!draft) return draft;
|
||||
|
||||
return draft.map((item) => {
|
||||
if (item.id !== navigationMenuItemId) return item;
|
||||
return { ...item, ...updates };
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return { updateNavigationMenuItemInDraft };
|
||||
};
|
||||
+10
-4
@@ -1,12 +1,13 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
|
||||
|
||||
import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem';
|
||||
import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavigationMenuItemFolder';
|
||||
import { FOLDER_ICON_DEFAULT } from '@/navigation-menu-item/constants/FolderIconDefault';
|
||||
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
|
||||
import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/types/processed-navigation-menu-item';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem';
|
||||
import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavigationMenuItemFolder';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { coreViewsState } from '@/views/states/coreViewState';
|
||||
import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@@ -19,6 +20,7 @@ export type FlatWorkspaceItem =
|
||||
| ProcessedNavigationMenuItem
|
||||
| (NavigationMenuItem & {
|
||||
itemType: NavigationMenuItemType.FOLDER;
|
||||
Icon: string;
|
||||
});
|
||||
|
||||
export type NavigationMenuItemClickParams = {
|
||||
@@ -56,7 +58,11 @@ export const useWorkspaceSectionItems = (): FlatWorkspaceItem[] => {
|
||||
FlatWorkspaceItem[]
|
||||
>((acc, item) => {
|
||||
if (isNavigationMenuItemFolder(item)) {
|
||||
acc.push({ ...item, itemType: NavigationMenuItemType.FOLDER });
|
||||
acc.push({
|
||||
...item,
|
||||
itemType: NavigationMenuItemType.FOLDER,
|
||||
Icon: item.icon ?? FOLDER_ICON_DEFAULT,
|
||||
});
|
||||
} else {
|
||||
const processedItem = processedObjectViewsById.get(item.id);
|
||||
if (!isDefined(processedItem)) {
|
||||
|
||||
@@ -12,18 +12,21 @@ import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
import { FavoritesDragContext } from '@/favorites/contexts/FavoritesDragContext';
|
||||
import { useHandleFavoriteDragAndDrop } from '@/favorites/hooks/useHandleFavoriteDragAndDrop';
|
||||
import { ADD_TO_NAV_SOURCE_DROPPABLE_ID } from '@/navigation-menu-item/constants/AddToNavSourceDroppableId';
|
||||
import { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds';
|
||||
import { NavigationDragSourceContext } from '@/navigation-menu-item/contexts/NavigationDragSourceContext';
|
||||
import { NavigationDropTargetContext } from '@/navigation-menu-item/contexts/NavigationDropTargetContext';
|
||||
import { NavigationMenuItemDragContext } from '@/navigation-menu-item/contexts/NavigationMenuItemDragContext';
|
||||
import { useHandleAddToNavigationDrop } from '@/navigation-menu-item/hooks/useHandleAddToNavigationDrop';
|
||||
import { useHandleNavigationMenuItemDragAndDrop } from '@/navigation-menu-item/hooks/useHandleNavigationMenuItemDragAndDrop';
|
||||
import { useHandleWorkspaceNavigationMenuItemDragAndDrop } from '@/navigation-menu-item/hooks/useHandleWorkspaceNavigationMenuItemDragAndDrop';
|
||||
import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/useNavigationMenuItemsDraftState';
|
||||
import { addToNavPayloadRegistryStateV2 } from '@/navigation-menu-item/states/addToNavPayloadRegistryStateV2';
|
||||
import { getDropTargetIdFromDestination } from '@/navigation-menu-item/utils/getDropTargetIdFromDestination';
|
||||
import { isWorkspaceDroppableId } from '@/navigation-menu-item/utils/isWorkspaceDroppableId';
|
||||
import { validateAndExtractWorkspaceFolderId } from '@/navigation-menu-item/utils/validateAndExtractWorkspaceFolderId';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type PageDragDropProviderProps = {
|
||||
children: ReactNode;
|
||||
@@ -45,7 +48,12 @@ export const PageDragDropProvider = ({
|
||||
const [forbiddenDropTargetId, setForbiddenDropTargetId] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [
|
||||
addToNavigationFallbackDestination,
|
||||
setAddToNavigationFallbackDestination,
|
||||
] = useState<{ droppableId: string; index: number } | null>(null);
|
||||
|
||||
const { workspaceNavigationMenuItems } = useNavigationMenuItemsDraftState();
|
||||
const { handleAddToNavigationDrop } = useHandleAddToNavigationDrop();
|
||||
const { handleNavigationMenuItemDragAndDrop } =
|
||||
useHandleNavigationMenuItemDragAndDrop();
|
||||
@@ -53,9 +61,22 @@ export const PageDragDropProvider = ({
|
||||
useHandleWorkspaceNavigationMenuItemDragAndDrop();
|
||||
const { handleFavoriteDragAndDrop } = useHandleFavoriteDragAndDrop();
|
||||
|
||||
const orphanItemCount = workspaceNavigationMenuItems.filter(
|
||||
(item) => !isDefined(item.folderId),
|
||||
).length;
|
||||
|
||||
const handleDragStart = (dragStart: DragStart) => {
|
||||
setIsDragging(true);
|
||||
setSourceDroppableId(dragStart.source.droppableId);
|
||||
if (dragStart.source.droppableId === ADD_TO_NAV_SOURCE_DROPPABLE_ID) {
|
||||
const defaultDestination = {
|
||||
droppableId:
|
||||
NavigationMenuItemDroppableIds.WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS,
|
||||
index: orphanItemCount,
|
||||
};
|
||||
setAddToNavigationFallbackDestination(defaultDestination);
|
||||
setActiveDropTargetId(getDropTargetIdFromDestination(defaultDestination));
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragUpdate = useRecoilCallback(
|
||||
@@ -65,36 +86,53 @@ export const PageDragDropProvider = ({
|
||||
if (source.droppableId !== ADD_TO_NAV_SOURCE_DROPPABLE_ID) {
|
||||
return;
|
||||
}
|
||||
if (!destination || !isWorkspaceDroppableId(destination.droppableId)) {
|
||||
setActiveDropTargetId(null);
|
||||
setForbiddenDropTargetId(null);
|
||||
return;
|
||||
}
|
||||
const dropTargetId = getDropTargetIdFromDestination(destination);
|
||||
setActiveDropTargetId(dropTargetId);
|
||||
if (
|
||||
destination !== null &&
|
||||
isWorkspaceDroppableId(destination.droppableId)
|
||||
) {
|
||||
setAddToNavigationFallbackDestination(destination);
|
||||
const dropTargetId = getDropTargetIdFromDestination(destination);
|
||||
setActiveDropTargetId(dropTargetId);
|
||||
|
||||
const payload =
|
||||
jotaiStore
|
||||
.get(addToNavPayloadRegistryStateV2.atom)
|
||||
.get(update.draggableId) ?? null;
|
||||
const folderId = validateAndExtractWorkspaceFolderId(
|
||||
destination.droppableId,
|
||||
);
|
||||
const isFolderOverFolder =
|
||||
payload?.type === 'folder' && folderId !== null;
|
||||
setForbiddenDropTargetId(isFolderOverFolder ? dropTargetId : null);
|
||||
const payload =
|
||||
jotaiStore
|
||||
.get(addToNavPayloadRegistryStateV2.atom)
|
||||
.get(update.draggableId) ?? null;
|
||||
const folderId = validateAndExtractWorkspaceFolderId(
|
||||
destination.droppableId,
|
||||
);
|
||||
const isFolderOverFolder =
|
||||
payload?.type === 'folder' && folderId !== null;
|
||||
setForbiddenDropTargetId(isFolderOverFolder ? dropTargetId : null);
|
||||
} else {
|
||||
setForbiddenDropTargetId(null);
|
||||
const fallback = addToNavigationFallbackDestination;
|
||||
setActiveDropTargetId(
|
||||
fallback ? getDropTargetIdFromDestination(fallback) : null,
|
||||
);
|
||||
}
|
||||
}) as OnDragUpdateResponder,
|
||||
[],
|
||||
[addToNavigationFallbackDestination],
|
||||
);
|
||||
|
||||
const handleDragEnd = (result: DropResult, provided: ResponderProvided) => {
|
||||
const isAddToNavigationSource =
|
||||
result.source.droppableId === ADD_TO_NAV_SOURCE_DROPPABLE_ID;
|
||||
const effectiveResult: DropResult =
|
||||
isAddToNavigationSource &&
|
||||
!result.destination &&
|
||||
addToNavigationFallbackDestination
|
||||
? { ...result, destination: addToNavigationFallbackDestination }
|
||||
: result;
|
||||
|
||||
setIsDragging(false);
|
||||
setSourceDroppableId(null);
|
||||
setActiveDropTargetId(null);
|
||||
setForbiddenDropTargetId(null);
|
||||
setAddToNavigationFallbackDestination(null);
|
||||
|
||||
if (result.source.droppableId === ADD_TO_NAV_SOURCE_DROPPABLE_ID) {
|
||||
handleAddToNavigationDrop(result, provided);
|
||||
if (isAddToNavigationSource) {
|
||||
handleAddToNavigationDrop(effectiveResult, provided);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -122,6 +160,7 @@ export const PageDragDropProvider = ({
|
||||
setActiveDropTargetId,
|
||||
forbiddenDropTargetId,
|
||||
setForbiddenDropTargetId,
|
||||
addToNavigationFallbackDestination,
|
||||
}}
|
||||
>
|
||||
<DragDropContext
|
||||
|
||||
+17
-6
@@ -1,3 +1,4 @@
|
||||
import { ObjectIconWithViewOverlay } from '@/navigation-menu-item/components/ObjectIconWithViewOverlay';
|
||||
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
|
||||
import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors';
|
||||
import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems';
|
||||
@@ -12,6 +13,9 @@ import { useRecoilValue } from 'recoil';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
import { getAppPath, isDefined } from 'twenty-shared/utils';
|
||||
import { Avatar, useIcons } from 'twenty-ui/display';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
|
||||
export type NavigationDrawerItemForObjectMetadataItemProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
@@ -33,6 +37,9 @@ export const NavigationDrawerItemForObjectMetadataItem = ({
|
||||
isDragging = false,
|
||||
}: NavigationDrawerItemForObjectMetadataItemProps) => {
|
||||
const theme = useTheme();
|
||||
const isNavigationMenuItemEditingEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED,
|
||||
);
|
||||
const iconColors = getNavigationMenuItemIconColors(theme);
|
||||
const lastVisitedViewPerObjectMetadataItem = useRecoilValue(
|
||||
lastVisitedViewPerObjectMetadataItemState,
|
||||
@@ -111,14 +118,18 @@ export const NavigationDrawerItemForObjectMetadataItem = ({
|
||||
/>
|
||||
)
|
||||
: isViewWithCustomName && isDefined(navigationMenuItem?.Icon)
|
||||
? getIcon(navigationMenuItem.Icon)
|
||||
? () => (
|
||||
<ObjectIconWithViewOverlay
|
||||
ObjectIcon={getIcon(objectMetadataItem.icon)}
|
||||
ViewIcon={getIcon(navigationMenuItem!.Icon!)}
|
||||
/>
|
||||
)
|
||||
: getIcon(objectMetadataItem.icon);
|
||||
|
||||
const iconBackgroundColor = isRecord
|
||||
? undefined
|
||||
: isViewWithCustomName
|
||||
? iconColors.view
|
||||
: iconColors.object;
|
||||
const iconBackgroundColor =
|
||||
isNavigationMenuItemEditingEnabled && !isRecord && !isViewWithCustomName
|
||||
? iconColors.object
|
||||
: undefined;
|
||||
|
||||
const secondaryLabel =
|
||||
isRecord || isViewWithCustomName
|
||||
|
||||
+39
-10
@@ -1,4 +1,6 @@
|
||||
import { NavigationDropTargetContext } from '@/navigation-menu-item/contexts/NavigationDropTargetContext';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Droppable } from '@hello-pangea/dnd';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useContext } from 'react';
|
||||
@@ -7,12 +9,12 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconLink, IconPlus } from 'twenty-ui/display';
|
||||
|
||||
import { NavigationItemDropTarget } from '@/navigation-menu-item/components/NavigationItemDropTarget';
|
||||
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
|
||||
import { useIsDropDisabledForSection } from '@/navigation-menu-item/hooks/useIsDropDisabledForSection';
|
||||
import { NavigationSections } from '@/navigation-menu-item/constants/NavigationSections.constants';
|
||||
import { WorkspaceNavigationMenuItemsFolder } from '@/navigation-menu-item/components/WorkspaceNavigationMenuItemsFolder';
|
||||
import { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds';
|
||||
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
|
||||
import { NavigationSections } from '@/navigation-menu-item/constants/NavigationSections.constants';
|
||||
import { NavigationMenuItemDragContext } from '@/navigation-menu-item/contexts/NavigationMenuItemDragContext';
|
||||
import { useIsDropDisabledForSection } from '@/navigation-menu-item/hooks/useIsDropDisabledForSection';
|
||||
import {
|
||||
type FlatWorkspaceItem,
|
||||
type NavigationMenuItemClickParams,
|
||||
@@ -25,15 +27,21 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
|
||||
import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper';
|
||||
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
||||
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
|
||||
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
|
||||
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
|
||||
import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection';
|
||||
import { coreViewsState } from '@/views/states/coreViewState';
|
||||
import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView';
|
||||
|
||||
const StyledWorkspaceDroppableList = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.betweenSiblingsGap};
|
||||
`;
|
||||
|
||||
type NavigationDrawerSectionForWorkspaceItemsProps = {
|
||||
sectionTitle: string;
|
||||
items: FlatWorkspaceItem[];
|
||||
@@ -70,8 +78,14 @@ export const NavigationDrawerSectionForWorkspaceItems = ({
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
const { isDragging } = useContext(NavigationMenuItemDragContext);
|
||||
const { addToNavigationFallbackDestination } = useContext(
|
||||
NavigationDropTargetContext,
|
||||
);
|
||||
|
||||
const flatItems = items.filter((item) => !isDefined(item.folderId));
|
||||
const isAddToNavigationDropTargetVisible =
|
||||
addToNavigationFallbackDestination?.droppableId ===
|
||||
NavigationMenuItemDroppableIds.WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS;
|
||||
const folderChildrenById = items.reduce<
|
||||
Map<string, ProcessedNavigationMenuItem[]>
|
||||
>((acc, item) => {
|
||||
@@ -140,7 +154,10 @@ export const NavigationDrawerSectionForWorkspaceItems = ({
|
||||
};
|
||||
};
|
||||
|
||||
if (flatItems.length === 0) {
|
||||
const isAddMenuItemButtonVisible =
|
||||
isEditMode && isDefined(onAddMenuItem) && !isDragging;
|
||||
|
||||
if (flatItems.length === 0 && !isAddToNavigationDropTargetVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -154,7 +171,7 @@ export const NavigationDrawerSectionForWorkspaceItems = ({
|
||||
alwaysShowRightIcon={isEditMode}
|
||||
/>
|
||||
</NavigationDrawerAnimatedCollapseWrapper>
|
||||
{isNavigationSectionOpen && (
|
||||
{(isNavigationSectionOpen || isAddToNavigationDropTargetVisible) && (
|
||||
<Droppable
|
||||
droppableId={
|
||||
NavigationMenuItemDroppableIds.WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS
|
||||
@@ -162,7 +179,7 @@ export const NavigationDrawerSectionForWorkspaceItems = ({
|
||||
isDropDisabled={workspaceDropDisabled}
|
||||
>
|
||||
{(provided) => (
|
||||
<div
|
||||
<StyledWorkspaceDroppableList
|
||||
ref={provided.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...provided.droppableProps}
|
||||
@@ -189,6 +206,7 @@ export const NavigationDrawerSectionForWorkspaceItems = ({
|
||||
<WorkspaceNavigationMenuItemsFolder
|
||||
folderId={item.id}
|
||||
folderName={item.name ?? 'Folder'}
|
||||
folderIconKey={item.Icon}
|
||||
navigationMenuItems={
|
||||
folderChildrenById.get(item.id) ?? []
|
||||
}
|
||||
@@ -308,9 +326,9 @@ export const NavigationDrawerSectionForWorkspaceItems = ({
|
||||
folderId={null}
|
||||
index={filteredItems.length}
|
||||
sectionId={NavigationSections.WORKSPACE}
|
||||
compact={!!(isEditMode && onAddMenuItem)}
|
||||
compact={!isAddMenuItemButtonVisible}
|
||||
>
|
||||
{isEditMode && onAddMenuItem && (
|
||||
{isAddMenuItemButtonVisible && (
|
||||
<NavigationDrawerItem
|
||||
Icon={IconPlus}
|
||||
label={t`Add menu item`}
|
||||
@@ -319,8 +337,19 @@ export const NavigationDrawerSectionForWorkspaceItems = ({
|
||||
/>
|
||||
)}
|
||||
</NavigationItemDropTarget>
|
||||
{addToNavigationFallbackDestination?.droppableId ===
|
||||
NavigationMenuItemDroppableIds.WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS &&
|
||||
addToNavigationFallbackDestination.index >
|
||||
filteredItems.length && (
|
||||
<NavigationItemDropTarget
|
||||
folderId={null}
|
||||
index={addToNavigationFallbackDestination.index}
|
||||
sectionId={NavigationSections.WORKSPACE}
|
||||
compact
|
||||
/>
|
||||
)}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
</StyledWorkspaceDroppableList>
|
||||
)}
|
||||
</Droppable>
|
||||
)}
|
||||
|
||||
+25
-3
@@ -1,9 +1,11 @@
|
||||
import { COMMAND_MENU_SEARCH_BAR_HEIGHT_MOBILE } from '@/command-menu/constants/CommandMenuSearchBarHeightMobile';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
|
||||
import { isNavigationMenuInEditModeStateV2 } from '@/navigation-menu-item/states/isNavigationMenuInEditModeStateV2';
|
||||
import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices';
|
||||
import { PAGE_HEADER_COMMAND_MENU_BUTTON_CLICK_OUTSIDE_ID } from '@/ui/layout/page-header/constants/PageHeaderCommandMenuButtonClickOutsideId';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { css, useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { i18n } from '@lingui/core';
|
||||
import { t } from '@lingui/core/macro';
|
||||
@@ -12,8 +14,20 @@ import { AppTooltip, TooltipDelay, TooltipPosition } from 'twenty-ui/display';
|
||||
import { AnimatedButton } from 'twenty-ui/input';
|
||||
import { getOsControlSymbol, useIsMobile } from 'twenty-ui/utilities';
|
||||
|
||||
const StyledButtonWrapper = styled.div`
|
||||
const StyledButtonWrapper = styled.div<{
|
||||
alignWithCommandMenuTopBar: boolean;
|
||||
}>`
|
||||
z-index: ${RootStackingContextZIndices.CommandMenuButton};
|
||||
${({ alignWithCommandMenuTopBar, theme }) =>
|
||||
alignWithCommandMenuTopBar &&
|
||||
css`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: ${COMMAND_MENU_SEARCH_BAR_HEIGHT_MOBILE}px;
|
||||
position: fixed;
|
||||
right: ${theme.spacing(3)};
|
||||
top: 0;
|
||||
`}
|
||||
`;
|
||||
|
||||
const StyledTooltipWrapper = styled.div`
|
||||
@@ -109,9 +123,15 @@ const AnimatedIcon = ({
|
||||
export const PageHeaderToggleCommandMenuButton = () => {
|
||||
const { toggleCommandMenu } = useCommandMenu();
|
||||
const isCommandMenuOpened = useRecoilValueV2(isCommandMenuOpenedStateV2);
|
||||
const isNavigationMenuInEditMode = useRecoilValueV2(
|
||||
isNavigationMenuInEditModeStateV2,
|
||||
);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const alignWithCommandMenuTopBar =
|
||||
isMobile && isNavigationMenuInEditMode && isCommandMenuOpened;
|
||||
|
||||
const ariaLabel = isCommandMenuOpened
|
||||
? t`Close command menu`
|
||||
: t`Open command menu`;
|
||||
@@ -119,7 +139,9 @@ export const PageHeaderToggleCommandMenuButton = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<StyledButtonWrapper>
|
||||
<StyledButtonWrapper
|
||||
alignWithCommandMenuTopBar={alignWithCommandMenuTopBar}
|
||||
>
|
||||
<div id="toggle-command-menu-button">
|
||||
<AnimatedButton
|
||||
animatedSvg={
|
||||
|
||||
+1
@@ -356,6 +356,7 @@ export const NavigationDrawerItem = ({
|
||||
as={
|
||||
to ? (isExternalLink ? 'a' : Link) : rightOptions ? 'div' : undefined
|
||||
}
|
||||
role={to ? undefined : rightOptions ? 'button' : undefined}
|
||||
to={isExternalLink ? undefined : to}
|
||||
href={isExternalLink ? to : undefined}
|
||||
target={isExternalLink ? '_blank' : undefined}
|
||||
|
||||
Reference in New Issue
Block a user