diff --git a/packages/twenty-front/src/loading/components/LeftPanelSkeletonLoader.tsx b/packages/twenty-front/src/loading/components/LeftPanelSkeletonLoader.tsx index c8de2f64c4..b3ea1eda95 100644 --- a/packages/twenty-front/src/loading/components/LeftPanelSkeletonLoader.tsx +++ b/packages/twenty-front/src/loading/components/LeftPanelSkeletonLoader.tsx @@ -4,7 +4,7 @@ import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; import { ANIMATION, BACKGROUND_LIGHT, GRAY_SCALE } from 'twenty-ui'; import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; -import { DESKTOP_NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/DesktopNavDrawerWidths'; +import { NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/NavDrawerWidths'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { MainNavigationDrawerItemsSkeletonLoader } from '~/loading/components/MainNavigationDrawerItemsSkeletonLoader'; @@ -47,14 +47,14 @@ const StyledSkeletonTitleContainer = styled.div` export const LeftPanelSkeletonLoader = () => { const isMobile = useIsMobile(); - const mobileWidth = isMobile ? 0 : '100%'; - const desktopWidth = !mobileWidth ? 12 : DESKTOP_NAV_DRAWER_WIDTHS.menu; return ( { const { favorites, handleReorderFavorite } = useFavorites(); const loading = useIsPrefetchLoading(); - const { toggleNavigationSection, isNavigationSectionOpenState } = useNavigationSection('Favorites'); const isNavigationSectionOpen = useRecoilValue(isNavigationSectionOpenState); @@ -58,54 +59,65 @@ export const CurrentWorkspaceMemberFavorites = () => { ) return <>; + const isGroup = currentWorkspaceMemberFavorites.length > 1; + + const draggableListContent = ( + + {currentWorkspaceMemberFavorites.map((favorite, index) => { + const { + id, + labelIdentifier, + avatarUrl, + avatarType, + link, + recordId, + } = favorite; + + return ( + ( + + )} + to={link} + /> + } + /> + ); + })} + + } + /> + ); + return ( - toggleNavigationSection()} - /> - {isNavigationSectionOpen && ( - - {currentWorkspaceMemberFavorites.map((favorite, index) => { - const { - id, - labelIdentifier, - avatarUrl, - avatarType, - link, - recordId, - } = favorite; - - return ( - ( - - )} - to={link} - /> - } - /> - ); - })} - - } + + toggleNavigationSection()} /> + + + {isNavigationSectionOpen && ( + + {draggableListContent} + )} ); diff --git a/packages/twenty-front/src/modules/navigation/components/AppNavigationDrawer.tsx b/packages/twenty-front/src/modules/navigation/components/AppNavigationDrawer.tsx index a6d3b1045c..2584736034 100644 --- a/packages/twenty-front/src/modules/navigation/components/AppNavigationDrawer.tsx +++ b/packages/twenty-front/src/modules/navigation/components/AppNavigationDrawer.tsx @@ -8,14 +8,14 @@ import { NavigationDrawer, NavigationDrawerProps, } from '@/ui/navigation/navigation-drawer/components/NavigationDrawer'; -import { isNavigationDrawerOpenState } from '@/ui/navigation/states/isNavigationDrawerOpenState'; + import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI'; -import { useIsSettingsPage } from '../hooks/useIsSettingsPage'; -import { currentMobileNavigationDrawerState } from '../states/currentMobileNavigationDrawerState'; +import { useIsSettingsDrawer } from '@/navigation/hooks/useIsSettingsDrawer'; import { AdvancedSettingsToggle } from '@/ui/navigation/link/components/AdvancedSettingsToggle'; +import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded'; import { MainNavigationDrawerItems } from './MainNavigationDrawerItems'; export type AppNavigationDrawerProps = { @@ -26,22 +26,14 @@ export const AppNavigationDrawer = ({ className, }: AppNavigationDrawerProps) => { const isMobile = useIsMobile(); - const isSettingsPage = useIsSettingsPage(); - const currentMobileNavigationDrawer = useRecoilValue( - currentMobileNavigationDrawerState, - ); - const setIsNavigationDrawerOpen = useSetRecoilState( - isNavigationDrawerOpenState, + const isSettingsDrawer = useIsSettingsDrawer(); + const setIsNavigationDrawerExpanded = useSetRecoilState( + isNavigationDrawerExpandedState, ); const currentWorkspace = useRecoilValue(currentWorkspaceState); - const isSettingsDrawer = isMobile - ? currentMobileNavigationDrawer === 'settings' - : isSettingsPage; - const drawerProps: NavigationDrawerProps = isSettingsDrawer ? { - isSubMenu: true, title: 'Exit Settings', children: , footer: , @@ -57,13 +49,12 @@ export const AppNavigationDrawer = ({ }; useEffect(() => { - setIsNavigationDrawerOpen(!isMobile); - }, [isMobile, setIsNavigationDrawerOpen]); + setIsNavigationDrawerExpanded(!isMobile); + }, [isMobile, setIsNavigationDrawerExpanded]); return ( { const isWorkspaceFavoriteEnabled = useIsFeatureEnabled( 'IS_WORKSPACE_FAVORITE_ENABLED', ); + const [isNavigationDrawerExpanded, setIsNavigationDrawerExpanded] = + useRecoilState(isNavigationDrawerExpandedState); + const setNavigationDrawerExpandedMemorized = useSetRecoilState( + navigationDrawerExpandedMemorizedState, + ); return ( <> @@ -38,6 +45,8 @@ export const MainNavigationDrawerItems = () => { label="Settings" to={'/settings/profile'} onClick={() => { + setNavigationDrawerExpandedMemorized(isNavigationDrawerExpanded); + setIsNavigationDrawerExpanded(true); setNavigationMemorizedUrl(location.pathname + location.search); }} Icon={IconSettings} diff --git a/packages/twenty-front/src/modules/navigation/components/MobileNavigationBar.tsx b/packages/twenty-front/src/modules/navigation/components/MobileNavigationBar.tsx index c22887b744..e64a8deda1 100644 --- a/packages/twenty-front/src/modules/navigation/components/MobileNavigationBar.tsx +++ b/packages/twenty-front/src/modules/navigation/components/MobileNavigationBar.tsx @@ -1,11 +1,9 @@ -import { useRecoilState } from 'recoil'; -import { IconComponent, IconList, IconSearch, IconSettings } from 'twenty-ui'; - import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState'; import { NavigationBar } from '@/ui/navigation/navigation-bar/components/NavigationBar'; -import { isNavigationDrawerOpenState } from '@/ui/navigation/states/isNavigationDrawerOpenState'; - +import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded'; +import { useRecoilState } from 'recoil'; +import { IconComponent, IconList, IconSearch, IconSettings } from 'twenty-ui'; import { useIsSettingsPage } from '../hooks/useIsSettingsPage'; import { currentMobileNavigationDrawerState } from '../states/currentMobileNavigationDrawerState'; @@ -15,13 +13,12 @@ export const MobileNavigationBar = () => { const [isCommandMenuOpened] = useRecoilState(isCommandMenuOpenedState); const { closeCommandMenu, openCommandMenu } = useCommandMenu(); const isSettingsPage = useIsSettingsPage(); - const [isNavigationDrawerOpen, setIsNavigationDrawerOpen] = useRecoilState( - isNavigationDrawerOpenState, - ); + const [isNavigationDrawerExpanded, setIsNavigationDrawerExpanded] = + useRecoilState(isNavigationDrawerExpandedState); const [currentMobileNavigationDrawer, setCurrentMobileNavigationDrawer] = useRecoilState(currentMobileNavigationDrawerState); - const activeItemName = isNavigationDrawerOpen + const activeItemName = isNavigationDrawerExpanded ? currentMobileNavigationDrawer : isCommandMenuOpened ? 'search' @@ -39,7 +36,7 @@ export const MobileNavigationBar = () => { Icon: IconList, onClick: () => { closeCommandMenu(); - setIsNavigationDrawerOpen( + setIsNavigationDrawerExpanded( (previousIsOpen) => activeItemName !== 'main' || !previousIsOpen, ); setCurrentMobileNavigationDrawer('main'); @@ -52,7 +49,7 @@ export const MobileNavigationBar = () => { if (!isCommandMenuOpened) { openCommandMenu(); } - setIsNavigationDrawerOpen(false); + setIsNavigationDrawerExpanded(false); }, }, { @@ -60,7 +57,7 @@ export const MobileNavigationBar = () => { Icon: IconSettings, onClick: () => { closeCommandMenu(); - setIsNavigationDrawerOpen( + setIsNavigationDrawerExpanded( (previousIsOpen) => activeItemName !== 'settings' || !previousIsOpen, ); setCurrentMobileNavigationDrawer('settings'); diff --git a/packages/twenty-front/src/modules/navigation/components/__stories__/AppNavigationDrawer.stories.tsx b/packages/twenty-front/src/modules/navigation/components/__stories__/AppNavigationDrawer.stories.tsx index 53acfde422..3bb6f1acaf 100644 --- a/packages/twenty-front/src/modules/navigation/components/__stories__/AppNavigationDrawer.stories.tsx +++ b/packages/twenty-front/src/modules/navigation/components/__stories__/AppNavigationDrawer.stories.tsx @@ -1,16 +1,17 @@ +import { Meta, StoryObj } from '@storybook/react'; import { useEffect } from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { Meta, StoryObj } from '@storybook/react'; import { useSetRecoilState } from 'recoil'; import { currentMobileNavigationDrawerState } from '@/navigation/states/currentMobileNavigationDrawerState'; import { AppPath } from '@/types/AppPath'; -import { isNavigationDrawerOpenState } from '@/ui/navigation/states/isNavigationDrawerOpenState'; + import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator'; import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; +import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded'; import { AppNavigationDrawer, AppNavigationDrawerProps, @@ -22,8 +23,8 @@ const MobileNavigationDrawerStateSetterEffect = ({ mobileNavigationDrawer?: 'main' | 'settings'; }) => { const isMobile = useIsMobile(); - const setIsNavigationDrawerOpen = useSetRecoilState( - isNavigationDrawerOpenState, + const setIsNavigationDrawerExpanded = useSetRecoilState( + isNavigationDrawerExpandedState, ); const setCurrentMobileNavigationDrawer = useSetRecoilState( currentMobileNavigationDrawerState, @@ -32,13 +33,13 @@ const MobileNavigationDrawerStateSetterEffect = ({ useEffect(() => { if (!isMobile) return; - setIsNavigationDrawerOpen(true); + setIsNavigationDrawerExpanded(true); setCurrentMobileNavigationDrawer(mobileNavigationDrawer); }, [ isMobile, mobileNavigationDrawer, setCurrentMobileNavigationDrawer, - setIsNavigationDrawerOpen, + setIsNavigationDrawerExpanded, ]); return null; diff --git a/packages/twenty-front/src/modules/navigation/hooks/useIsSettingsDrawer.ts b/packages/twenty-front/src/modules/navigation/hooks/useIsSettingsDrawer.ts new file mode 100644 index 0000000000..1ee9d50614 --- /dev/null +++ b/packages/twenty-front/src/modules/navigation/hooks/useIsSettingsDrawer.ts @@ -0,0 +1,15 @@ +import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage'; +import { useRecoilValue } from 'recoil'; +import { currentMobileNavigationDrawerState } from '@/navigation/states/currentMobileNavigationDrawerState'; +import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; + +export const useIsSettingsDrawer = () => { + const isMobile = useIsMobile(); + const isSettingsPage = useIsSettingsPage(); + const currentMobileNavigationDrawer = useRecoilValue( + currentMobileNavigationDrawerState, + ); + return isMobile + ? currentMobileNavigationDrawer === 'settings' + : isSettingsPage; +}; diff --git a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForObjectMetadataItems.tsx b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForObjectMetadataItems.tsx index b4cbd4e899..5e666e0cf5 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForObjectMetadataItems.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForObjectMetadataItems.tsx @@ -1,18 +1,18 @@ -import { useLocation } from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; -import { useIcons } from 'twenty-ui'; - import { useLastVisitedView } from '@/navigation/hooks/useLastVisitedView'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection'; -import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle'; +import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper'; import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem'; import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection'; import { getNavigationSubItemState } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemState'; import { View } from '@/views/types/View'; import { getObjectMetadataItemViews } from '@/views/utils/getObjectMetadataItemViews'; - +import { useLocation } from 'react-router-dom'; +import { useRecoilValue } from 'recoil'; +import { useIcons } from 'twenty-ui'; +import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle'; +import { NavigationDrawerItemsCollapsedContainer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemsCollapsedContainer'; const ORDERED_STANDARD_OBJECTS = [ 'person', 'company', @@ -38,107 +38,105 @@ export const NavigationDrawerSectionForObjectMetadataItems = ({ const { getIcon } = useIcons(); const currentPath = useLocation().pathname; - const { getLastVisitedViewIdFromObjectMetadataItemId } = useLastVisitedView(); - // TODO: refactor this by splitting into separate components + const renderObjectMetadataItems = () => { + return [ + ...objectMetadataItems + .filter((item) => ORDERED_STANDARD_OBJECTS.includes(item.nameSingular)) + .sort((objectMetadataItemA, objectMetadataItemB) => { + const indexA = ORDERED_STANDARD_OBJECTS.indexOf( + objectMetadataItemA.nameSingular, + ); + const indexB = ORDERED_STANDARD_OBJECTS.indexOf( + objectMetadataItemB.nameSingular, + ); + if (indexA === -1 || indexB === -1) { + return objectMetadataItemA.nameSingular.localeCompare( + objectMetadataItemB.nameSingular, + ); + } + return indexA - indexB; + }), + ...objectMetadataItems + .filter((item) => !ORDERED_STANDARD_OBJECTS.includes(item.nameSingular)) + .sort((objectMetadataItemA, objectMetadataItemB) => { + return new Date(objectMetadataItemA.createdAt) < + new Date(objectMetadataItemB.createdAt) + ? 1 + : -1; + }), + ].map((objectMetadataItem) => { + const objectMetadataViews = getObjectMetadataItemViews( + objectMetadataItem.id, + views, + ); + const lastVisitedViewId = getLastVisitedViewIdFromObjectMetadataItemId( + objectMetadataItem.id, + ); + const viewId = lastVisitedViewId ?? objectMetadataViews[0]?.id; + + const navigationPath = `/objects/${objectMetadataItem.namePlural}${ + viewId ? `?view=${viewId}` : '' + }`; + + const isActive = + currentPath === `/objects/${objectMetadataItem.namePlural}`; + const shouldSubItemsBeDisplayed = + isActive && objectMetadataViews.length > 1; + + const sortedObjectMetadataViews = [...objectMetadataViews].sort( + (viewA, viewB) => + viewA.key === 'INDEX' ? -1 : viewA.position - viewB.position, + ); + + const selectedSubItemIndex = sortedObjectMetadataViews.findIndex( + (view) => viewId === view.id, + ); + + const subItemArrayLength = sortedObjectMetadataViews.length; + + return ( + + + {shouldSubItemsBeDisplayed && + sortedObjectMetadataViews.map((view, index) => ( + + ))} + + ); + }); + }; + return ( objectMetadataItems.length > 0 && ( - toggleNavigationSection()} - /> - - {isNavigationSectionOpen && - [ - ...objectMetadataItems - .filter((item) => - ORDERED_STANDARD_OBJECTS.includes(item.nameSingular), - ) - .sort((objectMetadataItemA, objectMetadataItemB) => { - const indexA = ORDERED_STANDARD_OBJECTS.indexOf( - objectMetadataItemA.nameSingular, - ); - const indexB = ORDERED_STANDARD_OBJECTS.indexOf( - objectMetadataItemB.nameSingular, - ); - if (indexA === -1 || indexB === -1) { - return objectMetadataItemA.nameSingular.localeCompare( - objectMetadataItemB.nameSingular, - ); - } - return indexA - indexB; - }), - ...objectMetadataItems - .filter( - (item) => !ORDERED_STANDARD_OBJECTS.includes(item.nameSingular), - ) - .sort((objectMetadataItemA, objectMetadataItemB) => { - return new Date(objectMetadataItemA.createdAt) < - new Date(objectMetadataItemB.createdAt) - ? 1 - : -1; - }), - ].map((objectMetadataItem) => { - const objectMetadataViews = getObjectMetadataItemViews( - objectMetadataItem.id, - views, - ); - const lastVisitedViewId = - getLastVisitedViewIdFromObjectMetadataItemId( - objectMetadataItem.id, - ); - const viewId = lastVisitedViewId ?? objectMetadataViews[0]?.id; - - const navigationPath = `/objects/${objectMetadataItem.namePlural}${ - viewId ? `?view=${viewId}` : '' - }`; - - const shouldSubItemsBeDisplayed = - currentPath === `/objects/${objectMetadataItem.namePlural}` && - objectMetadataViews.length > 1; - - const sortedObjectMetadataViews = [...objectMetadataViews].sort( - (viewA, viewB) => - viewA.key === 'INDEX' ? -1 : viewA.position - viewB.position, - ); - - const selectedSubItemIndex = sortedObjectMetadataViews.findIndex( - (view) => viewId === view.id, - ); - - const subItemArrayLength = sortedObjectMetadataViews.length; - - return ( -
- - {shouldSubItemsBeDisplayed && - sortedObjectMetadataViews.map((view, index) => ( - - ))} -
- ); - })} + + toggleNavigationSection()} + /> + + {isNavigationSectionOpen && renderObjectMetadataItems()}
) ); diff --git a/packages/twenty-front/src/modules/ui/layout/page/DefaultLayout.tsx b/packages/twenty-front/src/modules/ui/layout/page/DefaultLayout.tsx index 0a05da3651..0f11f9d50e 100644 --- a/packages/twenty-front/src/modules/ui/layout/page/DefaultLayout.tsx +++ b/packages/twenty-front/src/modules/ui/layout/page/DefaultLayout.tsx @@ -8,7 +8,7 @@ import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage'; import { OBJECT_SETTINGS_WIDTH } from '@/settings/data-model/constants/ObjectSettings'; import { SignInBackgroundMockPage } from '@/sign-in-background-mock/components/SignInBackgroundMockPage'; import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal'; -import { DESKTOP_NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/DesktopNavDrawerWidths'; +import { NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/NavDrawerWidths'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { useScreenSize } from '@/ui/utilities/screen-size/hooks/useScreenSize'; import { css, Global, useTheme } from '@emotion/react'; @@ -84,7 +84,7 @@ export const DefaultLayout = () => { isSettingsPage && !isMobile ? (windowsWidth - (OBJECT_SETTINGS_WIDTH + - DESKTOP_NAV_DRAWER_WIDTHS.menu + + NAV_DRAWER_WIDTHS.menu.desktop.expanded + 64)) / 2 : 0, diff --git a/packages/twenty-front/src/modules/ui/layout/page/PageHeader.tsx b/packages/twenty-front/src/modules/ui/layout/page/PageHeader.tsx index ed9586bf96..332dee6a21 100644 --- a/packages/twenty-front/src/modules/ui/layout/page/PageHeader.tsx +++ b/packages/twenty-front/src/modules/ui/layout/page/PageHeader.tsx @@ -13,7 +13,8 @@ import { import { IconButton } from '@/ui/input/button/components/IconButton'; import { NavigationDrawerCollapseButton } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerCollapseButton'; -import { isNavigationDrawerOpenState } from '@/ui/navigation/states/isNavigationDrawerOpenState'; + +import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; export const PAGE_BAR_MIN_HEIGHT = 40; @@ -108,12 +109,14 @@ export const PageHeader = ({ }: PageHeaderProps) => { const isMobile = useIsMobile(); const theme = useTheme(); - const isNavigationDrawerOpen = useRecoilValue(isNavigationDrawerOpenState); + const isNavigationDrawerExpanded = useRecoilValue( + isNavigationDrawerExpandedState, + ); return ( - {!isMobile && !isNavigationDrawerOpen && ( + {!isMobile && !isNavigationDrawerExpanded && ( diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdownButton.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdownButton.tsx index 25d9340458..4fa845c6ac 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdownButton.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdownButton.tsx @@ -1,6 +1,6 @@ -import { useState } from 'react'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import { useState } from 'react'; import { useRecoilValue } from 'recoil'; import { IconChevronDown } from 'twenty-ui'; @@ -15,6 +15,7 @@ import { MULTI_WORKSPACE_DROPDOWN_ID } from '@/ui/navigation/navigation-drawer/c import { useWorkspaceSwitching } from '@/ui/navigation/navigation-drawer/hooks/useWorkspaceSwitching'; import { NavigationDrawerHotKeyScope } from '@/ui/navigation/navigation-drawer/types/NavigationDrawerHotKeyScope'; import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI'; +import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper'; const StyledLogo = styled.div<{ logo: string }>` background: url(${({ logo }) => logo}); @@ -37,8 +38,6 @@ const StyledContainer = styled.div` padding: calc(${({ theme }) => theme.spacing(1)} - 1px); width: 100%; - gap: ${({ theme }) => theme.spacing(1)}; - &:hover { background-color: ${({ theme }) => theme.background.transparent.lighter}; border: 1px solid ${({ theme }) => theme.border.color.medium}; @@ -48,6 +47,7 @@ const StyledContainer = styled.div` const StyledLabel = styled.div` align-items: center; display: flex; + margin: 0 ${({ theme }) => theme.spacing(1)}; `; const StyledIconChevronDown = styled(IconChevronDown)<{ disabled?: boolean }>` @@ -95,11 +95,15 @@ export const MultiWorkspaceDropdownButton = ({ ) ?? '' } /> - {currentWorkspace?.displayName ?? ''} - + + {currentWorkspace?.displayName ?? ''} + + + + } dropdownComponents={ diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawer.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawer.tsx index 997441038d..8fbd853a56 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawer.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawer.tsx @@ -1,49 +1,45 @@ -import { css, useTheme } from '@emotion/react'; +import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { motion } from 'framer-motion'; import { ReactNode, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { MOBILE_VIEWPORT } from 'twenty-ui'; -import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage'; -import { isNavigationDrawerOpenState } from '@/ui/navigation/states/isNavigationDrawerOpenState'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; -import { DESKTOP_NAV_DRAWER_WIDTHS } from '../constants/DesktopNavDrawerWidths'; +import { NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/NavDrawerWidths'; +import { isNavigationDrawerExpandedState } from '../../states/isNavigationDrawerExpanded'; import { NavigationDrawerBackButton } from './NavigationDrawerBackButton'; import { NavigationDrawerHeader } from './NavigationDrawerHeader'; +import { useIsSettingsDrawer } from '@/navigation/hooks/useIsSettingsDrawer'; export type NavigationDrawerProps = { children: ReactNode; className?: string; footer?: ReactNode; - isSubMenu?: boolean; logo?: string; title?: string; }; -const StyledAnimatedContainer = styled(motion.div)` - display: flex; - justify-content: end; -`; +const StyledAnimatedContainer = styled(motion.div)``; -const StyledContainer = styled.div<{ isSubMenu?: boolean }>` +const StyledContainer = styled.div<{ + isSettings?: boolean; + isMobile?: boolean; +}>` box-sizing: border-box; display: flex; flex-direction: column; + width: ${NAV_DRAWER_WIDTHS.menu.desktop.expanded}px; gap: ${({ theme }) => theme.spacing(3)}; height: 100%; - min-width: ${DESKTOP_NAV_DRAWER_WIDTHS.menu}px; - padding: ${({ theme }) => theme.spacing(3, 2, 4)}; - - ${({ isSubMenu, theme }) => - isSubMenu - ? css` - padding-left: ${theme.spacing(0)}; - padding-right: ${theme.spacing(8)}; - ` - : ''} + padding: ${({ theme, isSettings, isMobile }) => + isSettings + ? isMobile + ? theme.spacing(3, 8) + : theme.spacing(3, 8, 4, 0) + : theme.spacing(3, 2, 4)}; @media (max-width: ${MOBILE_VIEWPORT}px) { width: 100%; @@ -61,15 +57,16 @@ export const NavigationDrawer = ({ children, className, footer, - isSubMenu, logo, title, }: NavigationDrawerProps) => { const [isHovered, setIsHovered] = useState(false); const isMobile = useIsMobile(); + const isSettingsDrawer = useIsSettingsDrawer(); const theme = useTheme(); - const isNavigationDrawerOpen = useRecoilValue(isNavigationDrawerOpenState); - const isSettingsPage = useIsSettingsPage(); + const isNavigationDrawerExpanded = useRecoilValue( + isNavigationDrawerExpandedState, + ); const handleHover = () => { setIsHovered(true); @@ -79,30 +76,35 @@ export const NavigationDrawer = ({ setIsHovered(false); }; - const desktopWidth = !isNavigationDrawerOpen - ? 12 - : DESKTOP_NAV_DRAWER_WIDTHS.menu; + const desktopWidth = isNavigationDrawerExpanded + ? NAV_DRAWER_WIDTHS.menu.desktop.expanded + : NAV_DRAWER_WIDTHS.menu.desktop.collapsed; - const mobileWidth = isNavigationDrawerOpen ? '100%' : 0; + const mobileWidth = isNavigationDrawerExpanded + ? NAV_DRAWER_WIDTHS.menu.mobile.expanded + : NAV_DRAWER_WIDTHS.menu.mobile.collapsed; + + const navigationDrawerAnimate = { + width: isMobile ? mobileWidth : desktopWidth, + opacity: isNavigationDrawerExpanded || !isSettingsDrawer ? 1 : 0, + }; return ( - {isSubMenu && title ? ( + {isSettingsDrawer && title ? ( !isMobile && ) : ( { + const theme = useTheme(); + const isSettingsPage = useIsSettingsPage(); + const isNavigationDrawerExpanded = useRecoilValue( + isNavigationDrawerExpandedState, + ); + + if (isSettingsPage) { + return children; + } + + const animate: AnimationControls | TargetAndTransition = + isNavigationDrawerExpanded + ? { + opacity: 1, + width: 'auto', + height: 'auto', + pointerEvents: 'auto', + } + : { + opacity: 0, + width: 0, + height: 0, + pointerEvents: 'none', + }; + + return ( + + {children} + + ); +}; diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerBackButton.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerBackButton.tsx index 96490d7f0a..4860279661 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerBackButton.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerBackButton.tsx @@ -1,9 +1,11 @@ import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; import { IconX } from 'twenty-ui'; import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink'; +import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded'; +import { navigationDrawerExpandedMemorizedState } from '@/ui/navigation/states/navigationDrawerExpandedMemorizedState'; import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; type NavigationDrawerBackButtonProps = { @@ -43,9 +45,22 @@ export const NavigationDrawerBackButton = ({ const theme = useTheme(); const navigationMemorizedUrl = useRecoilValue(navigationMemorizedUrlState); + const setIsNavigationDrawerExpanded = useSetRecoilState( + isNavigationDrawerExpandedState, + ); + const navigationDrawerExpandedMemorized = useRecoilValue( + navigationDrawerExpandedMemorizedState, + ); + return ( - + + setIsNavigationDrawerExpanded(navigationDrawerExpandedMemorized) + } + > theme.border.radius.md}; @@ -33,15 +32,17 @@ export const NavigationDrawerCollapseButton = ({ className, direction = 'left', }: NavigationDrawerCollapseButtonProps) => { - const setIsNavigationDrawerOpen = useSetRecoilState( - isNavigationDrawerOpenState, + const setIsNavigationDrawerExpanded = useSetRecoilState( + isNavigationDrawerExpandedState, ); return ( - setIsNavigationDrawerOpen((previousIsOpen) => !previousIsOpen) + setIsNavigationDrawerExpanded( + (previousIsExpanded) => !previousIsExpanded, + ) } > ` +const StyledContainer = styled.div` align-items: center; display: flex; - gap: ${({ theme, isMultiWorkspace }) => - !isMultiWorkspace ? theme.spacing(2) : null}; height: ${({ theme }) => theme.spacing(8)}; user-select: none; `; +const StyledSingleWorkspaceContainer = styled(StyledContainer)` + gap: ${({ theme }) => theme.spacing(2)}; +`; const StyledLogo = styled.div<{ logo: string }>` background: url(${({ logo }) => logo}); @@ -57,21 +60,25 @@ export const NavigationDrawerHeader = ({ const isMobile = useIsMobile(); const workspaces = useRecoilValue(workspacesState); const isMultiWorkspace = workspaces !== null && workspaces.length > 1; + const isNavigationDrawerExpanded = useRecoilValue( + isNavigationDrawerExpandedState, + ); return ( - + {isMultiWorkspace ? ( ) : ( - <> + - {name} - + + {name} + + )} - - {!isMobile && ( + {!isMobile && isNavigationDrawerExpanded && ( ; +> & { isNavigationDrawerExpanded: boolean }; const StyledItem = styled('div', { shouldForwardProp: (prop) => @@ -65,9 +68,8 @@ const StyledItem = styled('div', { }}; cursor: ${(props) => (props.soon ? 'default' : 'pointer')}; display: flex; - font-family: 'Inter'; + font-family: ${({ theme }) => theme.font.family}; font-size: ${({ theme }) => theme.font.size.md}; - gap: ${({ theme }) => theme.spacing(2)}; padding-bottom: ${({ theme }) => theme.spacing(1)}; padding-left: ${({ theme }) => theme.spacing(1)}; @@ -78,7 +80,12 @@ const StyledItem = styled('div', { indentationLevel === 2 ? '2px' : '0'}; pointer-events: ${(props) => (props.soon ? 'none' : 'auto')}; - width: 100%; + + width: ${(props) => + !props.isNavigationDrawerExpanded + ? `${NAV_DRAWER_WIDTHS.menu.desktop.collapsed - 24}px` + : '100%'}; + :hover { background: ${({ theme }) => theme.background.transparent.light}; color: ${(props) => @@ -96,9 +103,14 @@ const StyledItem = styled('div', { } `; +const StyledItemElementsContainer = styled.div` + align-items: center; + display: flex; + gap: ${({ theme }) => theme.spacing(2)}; +`; + const StyledItemLabel = styled.div` font-weight: ${({ theme }) => theme.font.weight.medium}; - overflow: hidden; text-overflow: ellipsis; white-space: nowrap; `; @@ -111,7 +123,6 @@ const StyledItemCount = styled.div` display: flex; font-size: ${({ theme }) => theme.font.size.xs}; font-weight: ${({ theme }) => theme.font.weight.semiBold}; - height: 16px; justify-content: center; margin-left: auto; @@ -151,16 +162,15 @@ export const NavigationDrawerItem = ({ }: NavigationDrawerItemProps) => { const theme = useTheme(); const isMobile = useIsMobile(); + const isSettingsPage = useIsSettingsPage(); const navigate = useNavigate(); - const setIsNavigationDrawerOpen = useSetRecoilState( - isNavigationDrawerOpenState, - ); - + const [isNavigationDrawerExpanded, setIsNavigationDrawerExpanded] = + useRecoilState(isNavigationDrawerExpandedState); const showBreadcrumb = indentationLevel === 2; const handleItemClick = () => { if (isMobile) { - setIsNavigationDrawerOpen(false); + setIsNavigationDrawerExpanded(false); } if (isDefined(onClick)) { @@ -185,25 +195,51 @@ export const NavigationDrawerItem = ({ as={to ? Link : 'div'} to={to ? to : undefined} indentationLevel={indentationLevel} + isNavigationDrawerExpanded={isNavigationDrawerExpanded} > {showBreadcrumb && ( - - )} - {Icon && ( - - )} - {label} - {soon && } - {!!count && {count}} - {keyboard && ( - - {keyboard} - + + + )} + + {Icon && ( + + )} + + + {label} + + + {soon && ( + + + + )} + + {!!count && ( + + {count} + + )} + + {keyboard && ( + + + {keyboard} + + + )} + ); diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerItemBreadcrumb.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerItemBreadcrumb.tsx index 47e8bd1208..6c39985441 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerItemBreadcrumb.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerItemBreadcrumb.tsx @@ -6,9 +6,10 @@ export type NavigationDrawerItemBreadcrumbProps = { }; const StyledNavigationDrawerItemBreadcrumbContainer = styled.div` - margin-left: 7.5px; - height: 28px; + + margin-left: 7.5px; + margin-right: ${({ theme }) => theme.spacing(2)}; width: 9px; `; diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerItemsCollapsedContainer.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerItemsCollapsedContainer.tsx new file mode 100644 index 0000000000..550793621c --- /dev/null +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerItemsCollapsedContainer.tsx @@ -0,0 +1,52 @@ +import styled from '@emotion/styled'; +import { ReactNode } from 'react'; +import { useRecoilValue } from 'recoil'; +import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded'; +import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage'; +import { AnimationControls, motion, TargetAndTransition } from 'framer-motion'; +import { useTheme } from '@emotion/react'; + +const StyledAnimationGroupContainer = styled(motion.div)``; + +type NavigationDrawerItemsCollapsedContainerProps = { + isGroup?: boolean; + children: ReactNode; +}; + +export const NavigationDrawerItemsCollapsedContainer = ({ + isGroup = false, + children, +}: NavigationDrawerItemsCollapsedContainerProps) => { + const theme = useTheme(); + const isSettingsPage = useIsSettingsPage(); + const isNavigationDrawerExpanded = useRecoilValue( + isNavigationDrawerExpandedState, + ); + const isExpanded = isNavigationDrawerExpanded || isSettingsPage; + let animate: AnimationControls | TargetAndTransition = { + width: 'auto', + backgroundColor: 'transparent', + border: 'none', + }; + if (!isExpanded) { + animate = { width: 24 }; + if (isGroup) { + animate = { + width: 24, + backgroundColor: theme.background.transparent.lighter, + border: `1px solid ${theme.background.transparent.lighter}`, + borderRadius: theme.border.radius.sm, + }; + } + } + + return ( + + {children} + + ); +}; diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle.tsx index d208dccf11..aeeffa88d3 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle.tsx @@ -1,8 +1,10 @@ import styled from '@emotion/styled'; import { currentUserState } from '@/auth/states/currentUserState'; +import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage'; import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading'; import { NavigationDrawerSectionTitleSkeletonLoader } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitleSkeletonLoader'; +import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-ui'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; @@ -37,9 +39,22 @@ export const NavigationDrawerSectionTitle = ({ }: NavigationDrawerSectionTitleProps) => { const currentUser = useRecoilValue(currentUserState); const loading = useIsPrefetchLoading(); + const isNavigationDrawerExpanded = useRecoilValue( + isNavigationDrawerExpandedState, + ); + + const isSettingsPage = useIsSettingsPage(); if (loading && isDefined(currentUser)) { return ; } - return {label}; + return ( + + {label} + + ); }; diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/__stories__/NavigationDrawer.stories.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/__stories__/NavigationDrawer.stories.tsx index dfbe324297..1fe45b7822 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/__stories__/NavigationDrawer.stories.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/__stories__/NavigationDrawer.stories.tsx @@ -53,6 +53,11 @@ export const Default: Story = { Icon={IconBell} soon={true} /> + diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/constants/DesktopNavDrawerWidths.ts b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/constants/DesktopNavDrawerWidths.ts deleted file mode 100644 index fe5462310d..0000000000 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/constants/DesktopNavDrawerWidths.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const DESKTOP_NAV_DRAWER_WIDTHS = { - menu: 220, -}; diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/constants/NavDrawerWidths.ts b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/constants/NavDrawerWidths.ts new file mode 100644 index 0000000000..3abe700798 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/constants/NavDrawerWidths.ts @@ -0,0 +1,12 @@ +export const NAV_DRAWER_WIDTHS = { + menu: { + mobile: { + collapsed: 0, + expanded: '100%', + }, + desktop: { + collapsed: 40, + expanded: 220, + }, + }, +}; diff --git a/packages/twenty-front/src/modules/ui/navigation/states/isNavigationDrawerOpenState.ts b/packages/twenty-front/src/modules/ui/navigation/states/isNavigationDrawerExpanded.ts similarity index 63% rename from packages/twenty-front/src/modules/ui/navigation/states/isNavigationDrawerOpenState.ts rename to packages/twenty-front/src/modules/ui/navigation/states/isNavigationDrawerExpanded.ts index b5496b4a57..7155d06078 100644 --- a/packages/twenty-front/src/modules/ui/navigation/states/isNavigationDrawerOpenState.ts +++ b/packages/twenty-front/src/modules/ui/navigation/states/isNavigationDrawerExpanded.ts @@ -3,7 +3,7 @@ import { MOBILE_VIEWPORT } from 'twenty-ui'; const isMobile = window.innerWidth <= MOBILE_VIEWPORT; -export const isNavigationDrawerOpenState = atom({ - key: 'isNavigationDrawerOpen', +export const isNavigationDrawerExpandedState = atom({ + key: 'isNavigationDrawerExpanded', default: !isMobile, }); diff --git a/packages/twenty-front/src/modules/ui/navigation/states/navigationDrawerExpandedMemorizedState.ts b/packages/twenty-front/src/modules/ui/navigation/states/navigationDrawerExpandedMemorizedState.ts new file mode 100644 index 0000000000..cdeff16e74 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/navigation/states/navigationDrawerExpandedMemorizedState.ts @@ -0,0 +1,9 @@ +import { atom } from 'recoil'; +import { MOBILE_VIEWPORT } from 'twenty-ui'; + +const isMobile = window.innerWidth <= MOBILE_VIEWPORT; + +export const navigationDrawerExpandedMemorizedState = atom({ + key: 'navigationDrawerExpandedMemorized', + default: !isMobile, +});