mirror of
https://github.com/twentyhq/twenty.git
synced 2024-11-25 20:00:34 +03:00
feat - Compact sidebar (#7414)
This commit is contained in:
parent
c0610419c2
commit
a9deede9ba
@ -4,7 +4,7 @@ import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
|
|||||||
import { ANIMATION, BACKGROUND_LIGHT, GRAY_SCALE } from 'twenty-ui';
|
import { ANIMATION, BACKGROUND_LIGHT, GRAY_SCALE } from 'twenty-ui';
|
||||||
|
|
||||||
import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
|
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 { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
import { MainNavigationDrawerItemsSkeletonLoader } from '~/loading/components/MainNavigationDrawerItemsSkeletonLoader';
|
import { MainNavigationDrawerItemsSkeletonLoader } from '~/loading/components/MainNavigationDrawerItemsSkeletonLoader';
|
||||||
|
|
||||||
@ -47,14 +47,14 @@ const StyledSkeletonTitleContainer = styled.div`
|
|||||||
|
|
||||||
export const LeftPanelSkeletonLoader = () => {
|
export const LeftPanelSkeletonLoader = () => {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const mobileWidth = isMobile ? 0 : '100%';
|
|
||||||
const desktopWidth = !mobileWidth ? 12 : DESKTOP_NAV_DRAWER_WIDTHS.menu;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledAnimatedContainer
|
<StyledAnimatedContainer
|
||||||
initial={false}
|
initial={false}
|
||||||
animate={{
|
animate={{
|
||||||
width: isMobile ? mobileWidth : desktopWidth,
|
width: isMobile
|
||||||
|
? NAV_DRAWER_WIDTHS.menu.mobile.collapsed
|
||||||
|
: NAV_DRAWER_WIDTHS.menu.desktop.expanded,
|
||||||
opacity: isMobile ? 0 : 1,
|
opacity: isMobile ? 0 : 1,
|
||||||
}}
|
}}
|
||||||
transition={{
|
transition={{
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { BACKGROUND_LIGHT, MOBILE_VIEWPORT } from 'twenty-ui';
|
import { BACKGROUND_LIGHT, MOBILE_VIEWPORT } from 'twenty-ui';
|
||||||
|
|
||||||
import { DESKTOP_NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/DesktopNavDrawerWidths';
|
import { NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/NavDrawerWidths';
|
||||||
import { LeftPanelSkeletonLoader } from '~/loading/components/LeftPanelSkeletonLoader';
|
import { LeftPanelSkeletonLoader } from '~/loading/components/LeftPanelSkeletonLoader';
|
||||||
import { RightPanelSkeletonLoader } from '~/loading/components/RightPanelSkeletonLoader';
|
import { RightPanelSkeletonLoader } from '~/loading/components/RightPanelSkeletonLoader';
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ const StyledContainer = styled.div`
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
height: 100dvh;
|
height: 100dvh;
|
||||||
min-width: ${DESKTOP_NAV_DRAWER_WIDTHS.menu}px;
|
min-width: ${NAV_DRAWER_WIDTHS.menu.desktop.expanded}px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 12px 8px 12px 8px;
|
padding: 12px 8px 12px 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -13,6 +13,8 @@ import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/us
|
|||||||
|
|
||||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||||
import { useFavorites } from '../hooks/useFavorites';
|
import { useFavorites } from '../hooks/useFavorites';
|
||||||
|
import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper';
|
||||||
|
import { NavigationDrawerItemsCollapsedContainer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemsCollapsedContainer';
|
||||||
|
|
||||||
const StyledContainer = styled(NavigationDrawerSection)`
|
const StyledContainer = styled(NavigationDrawerSection)`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -39,7 +41,6 @@ export const CurrentWorkspaceMemberFavorites = () => {
|
|||||||
|
|
||||||
const { favorites, handleReorderFavorite } = useFavorites();
|
const { favorites, handleReorderFavorite } = useFavorites();
|
||||||
const loading = useIsPrefetchLoading();
|
const loading = useIsPrefetchLoading();
|
||||||
|
|
||||||
const { toggleNavigationSection, isNavigationSectionOpenState } =
|
const { toggleNavigationSection, isNavigationSectionOpenState } =
|
||||||
useNavigationSection('Favorites');
|
useNavigationSection('Favorites');
|
||||||
const isNavigationSectionOpen = useRecoilValue(isNavigationSectionOpenState);
|
const isNavigationSectionOpen = useRecoilValue(isNavigationSectionOpenState);
|
||||||
@ -58,54 +59,65 @@ export const CurrentWorkspaceMemberFavorites = () => {
|
|||||||
)
|
)
|
||||||
return <></>;
|
return <></>;
|
||||||
|
|
||||||
|
const isGroup = currentWorkspaceMemberFavorites.length > 1;
|
||||||
|
|
||||||
|
const draggableListContent = (
|
||||||
|
<DraggableList
|
||||||
|
onDragEnd={handleReorderFavorite}
|
||||||
|
draggableItems={
|
||||||
|
<>
|
||||||
|
{currentWorkspaceMemberFavorites.map((favorite, index) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
labelIdentifier,
|
||||||
|
avatarUrl,
|
||||||
|
avatarType,
|
||||||
|
link,
|
||||||
|
recordId,
|
||||||
|
} = favorite;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DraggableItem
|
||||||
|
key={id}
|
||||||
|
draggableId={id}
|
||||||
|
index={index}
|
||||||
|
itemComponent={
|
||||||
|
<StyledNavigationDrawerItem
|
||||||
|
key={id}
|
||||||
|
label={labelIdentifier}
|
||||||
|
Icon={() => (
|
||||||
|
<StyledAvatar
|
||||||
|
placeholderColorSeed={recordId}
|
||||||
|
avatarUrl={avatarUrl}
|
||||||
|
type={avatarType}
|
||||||
|
placeholder={labelIdentifier}
|
||||||
|
className="fav-avatar"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
to={link}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<NavigationDrawerSectionTitle
|
<NavigationDrawerAnimatedCollapseWrapper>
|
||||||
label="Favorites"
|
<NavigationDrawerSectionTitle
|
||||||
onClick={() => toggleNavigationSection()}
|
label="Favorites"
|
||||||
/>
|
onClick={() => toggleNavigationSection()}
|
||||||
{isNavigationSectionOpen && (
|
|
||||||
<DraggableList
|
|
||||||
onDragEnd={handleReorderFavorite}
|
|
||||||
draggableItems={
|
|
||||||
<>
|
|
||||||
{currentWorkspaceMemberFavorites.map((favorite, index) => {
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
labelIdentifier,
|
|
||||||
avatarUrl,
|
|
||||||
avatarType,
|
|
||||||
link,
|
|
||||||
recordId,
|
|
||||||
} = favorite;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DraggableItem
|
|
||||||
key={id}
|
|
||||||
draggableId={id}
|
|
||||||
index={index}
|
|
||||||
itemComponent={
|
|
||||||
<StyledNavigationDrawerItem
|
|
||||||
key={id}
|
|
||||||
label={labelIdentifier}
|
|
||||||
Icon={() => (
|
|
||||||
<StyledAvatar
|
|
||||||
placeholderColorSeed={recordId}
|
|
||||||
avatarUrl={avatarUrl}
|
|
||||||
type={avatarType}
|
|
||||||
placeholder={labelIdentifier}
|
|
||||||
className="fav-avatar"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
to={link}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
</NavigationDrawerAnimatedCollapseWrapper>
|
||||||
|
|
||||||
|
{isNavigationSectionOpen && (
|
||||||
|
<NavigationDrawerItemsCollapsedContainer isGroup={isGroup}>
|
||||||
|
{draggableListContent}
|
||||||
|
</NavigationDrawerItemsCollapsedContainer>
|
||||||
)}
|
)}
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
|
@ -8,14 +8,14 @@ import {
|
|||||||
NavigationDrawer,
|
NavigationDrawer,
|
||||||
NavigationDrawerProps,
|
NavigationDrawerProps,
|
||||||
} from '@/ui/navigation/navigation-drawer/components/NavigationDrawer';
|
} from '@/ui/navigation/navigation-drawer/components/NavigationDrawer';
|
||||||
import { isNavigationDrawerOpenState } from '@/ui/navigation/states/isNavigationDrawerOpenState';
|
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI';
|
import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI';
|
||||||
|
|
||||||
import { useIsSettingsPage } from '../hooks/useIsSettingsPage';
|
import { useIsSettingsDrawer } from '@/navigation/hooks/useIsSettingsDrawer';
|
||||||
import { currentMobileNavigationDrawerState } from '../states/currentMobileNavigationDrawerState';
|
|
||||||
|
|
||||||
import { AdvancedSettingsToggle } from '@/ui/navigation/link/components/AdvancedSettingsToggle';
|
import { AdvancedSettingsToggle } from '@/ui/navigation/link/components/AdvancedSettingsToggle';
|
||||||
|
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||||
import { MainNavigationDrawerItems } from './MainNavigationDrawerItems';
|
import { MainNavigationDrawerItems } from './MainNavigationDrawerItems';
|
||||||
|
|
||||||
export type AppNavigationDrawerProps = {
|
export type AppNavigationDrawerProps = {
|
||||||
@ -26,22 +26,14 @@ export const AppNavigationDrawer = ({
|
|||||||
className,
|
className,
|
||||||
}: AppNavigationDrawerProps) => {
|
}: AppNavigationDrawerProps) => {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const isSettingsPage = useIsSettingsPage();
|
const isSettingsDrawer = useIsSettingsDrawer();
|
||||||
const currentMobileNavigationDrawer = useRecoilValue(
|
const setIsNavigationDrawerExpanded = useSetRecoilState(
|
||||||
currentMobileNavigationDrawerState,
|
isNavigationDrawerExpandedState,
|
||||||
);
|
|
||||||
const setIsNavigationDrawerOpen = useSetRecoilState(
|
|
||||||
isNavigationDrawerOpenState,
|
|
||||||
);
|
);
|
||||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||||
|
|
||||||
const isSettingsDrawer = isMobile
|
|
||||||
? currentMobileNavigationDrawer === 'settings'
|
|
||||||
: isSettingsPage;
|
|
||||||
|
|
||||||
const drawerProps: NavigationDrawerProps = isSettingsDrawer
|
const drawerProps: NavigationDrawerProps = isSettingsDrawer
|
||||||
? {
|
? {
|
||||||
isSubMenu: true,
|
|
||||||
title: 'Exit Settings',
|
title: 'Exit Settings',
|
||||||
children: <SettingsNavigationDrawerItems />,
|
children: <SettingsNavigationDrawerItems />,
|
||||||
footer: <AdvancedSettingsToggle />,
|
footer: <AdvancedSettingsToggle />,
|
||||||
@ -57,13 +49,12 @@ export const AppNavigationDrawer = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsNavigationDrawerOpen(!isMobile);
|
setIsNavigationDrawerExpanded(!isMobile);
|
||||||
}, [isMobile, setIsNavigationDrawerOpen]);
|
}, [isMobile, setIsNavigationDrawerExpanded]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationDrawer
|
<NavigationDrawer
|
||||||
className={className}
|
className={className}
|
||||||
isSubMenu={drawerProps.isSubMenu}
|
|
||||||
logo={drawerProps.logo}
|
logo={drawerProps.logo}
|
||||||
title={drawerProps.title}
|
title={drawerProps.title}
|
||||||
footer={drawerProps.footer}
|
footer={drawerProps.footer}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { useSetRecoilState } from 'recoil';
|
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||||
import { IconSearch, IconSettings } from 'twenty-ui';
|
import { IconSearch, IconSettings } from 'twenty-ui';
|
||||||
|
|
||||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||||
@ -9,6 +9,8 @@ import { NavigationDrawerOpenedSection } from '@/object-metadata/components/Navi
|
|||||||
import { NavigationDrawerSectionForObjectMetadataItemsWrapper } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItemsWrapper';
|
import { NavigationDrawerSectionForObjectMetadataItemsWrapper } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItemsWrapper';
|
||||||
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
||||||
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
|
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
|
||||||
|
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||||
|
import { navigationDrawerExpandedMemorizedState } from '@/ui/navigation/states/navigationDrawerExpandedMemorizedState';
|
||||||
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
@ -23,6 +25,11 @@ export const MainNavigationDrawerItems = () => {
|
|||||||
const isWorkspaceFavoriteEnabled = useIsFeatureEnabled(
|
const isWorkspaceFavoriteEnabled = useIsFeatureEnabled(
|
||||||
'IS_WORKSPACE_FAVORITE_ENABLED',
|
'IS_WORKSPACE_FAVORITE_ENABLED',
|
||||||
);
|
);
|
||||||
|
const [isNavigationDrawerExpanded, setIsNavigationDrawerExpanded] =
|
||||||
|
useRecoilState(isNavigationDrawerExpandedState);
|
||||||
|
const setNavigationDrawerExpandedMemorized = useSetRecoilState(
|
||||||
|
navigationDrawerExpandedMemorizedState,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -38,6 +45,8 @@ export const MainNavigationDrawerItems = () => {
|
|||||||
label="Settings"
|
label="Settings"
|
||||||
to={'/settings/profile'}
|
to={'/settings/profile'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
setNavigationDrawerExpandedMemorized(isNavigationDrawerExpanded);
|
||||||
|
setIsNavigationDrawerExpanded(true);
|
||||||
setNavigationMemorizedUrl(location.pathname + location.search);
|
setNavigationMemorizedUrl(location.pathname + location.search);
|
||||||
}}
|
}}
|
||||||
Icon={IconSettings}
|
Icon={IconSettings}
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import { useRecoilState } from 'recoil';
|
|
||||||
import { IconComponent, IconList, IconSearch, IconSettings } from 'twenty-ui';
|
|
||||||
|
|
||||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||||
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
||||||
import { NavigationBar } from '@/ui/navigation/navigation-bar/components/NavigationBar';
|
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 { useIsSettingsPage } from '../hooks/useIsSettingsPage';
|
||||||
import { currentMobileNavigationDrawerState } from '../states/currentMobileNavigationDrawerState';
|
import { currentMobileNavigationDrawerState } from '../states/currentMobileNavigationDrawerState';
|
||||||
|
|
||||||
@ -15,13 +13,12 @@ export const MobileNavigationBar = () => {
|
|||||||
const [isCommandMenuOpened] = useRecoilState(isCommandMenuOpenedState);
|
const [isCommandMenuOpened] = useRecoilState(isCommandMenuOpenedState);
|
||||||
const { closeCommandMenu, openCommandMenu } = useCommandMenu();
|
const { closeCommandMenu, openCommandMenu } = useCommandMenu();
|
||||||
const isSettingsPage = useIsSettingsPage();
|
const isSettingsPage = useIsSettingsPage();
|
||||||
const [isNavigationDrawerOpen, setIsNavigationDrawerOpen] = useRecoilState(
|
const [isNavigationDrawerExpanded, setIsNavigationDrawerExpanded] =
|
||||||
isNavigationDrawerOpenState,
|
useRecoilState(isNavigationDrawerExpandedState);
|
||||||
);
|
|
||||||
const [currentMobileNavigationDrawer, setCurrentMobileNavigationDrawer] =
|
const [currentMobileNavigationDrawer, setCurrentMobileNavigationDrawer] =
|
||||||
useRecoilState(currentMobileNavigationDrawerState);
|
useRecoilState(currentMobileNavigationDrawerState);
|
||||||
|
|
||||||
const activeItemName = isNavigationDrawerOpen
|
const activeItemName = isNavigationDrawerExpanded
|
||||||
? currentMobileNavigationDrawer
|
? currentMobileNavigationDrawer
|
||||||
: isCommandMenuOpened
|
: isCommandMenuOpened
|
||||||
? 'search'
|
? 'search'
|
||||||
@ -39,7 +36,7 @@ export const MobileNavigationBar = () => {
|
|||||||
Icon: IconList,
|
Icon: IconList,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
closeCommandMenu();
|
closeCommandMenu();
|
||||||
setIsNavigationDrawerOpen(
|
setIsNavigationDrawerExpanded(
|
||||||
(previousIsOpen) => activeItemName !== 'main' || !previousIsOpen,
|
(previousIsOpen) => activeItemName !== 'main' || !previousIsOpen,
|
||||||
);
|
);
|
||||||
setCurrentMobileNavigationDrawer('main');
|
setCurrentMobileNavigationDrawer('main');
|
||||||
@ -52,7 +49,7 @@ export const MobileNavigationBar = () => {
|
|||||||
if (!isCommandMenuOpened) {
|
if (!isCommandMenuOpened) {
|
||||||
openCommandMenu();
|
openCommandMenu();
|
||||||
}
|
}
|
||||||
setIsNavigationDrawerOpen(false);
|
setIsNavigationDrawerExpanded(false);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -60,7 +57,7 @@ export const MobileNavigationBar = () => {
|
|||||||
Icon: IconSettings,
|
Icon: IconSettings,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
closeCommandMenu();
|
closeCommandMenu();
|
||||||
setIsNavigationDrawerOpen(
|
setIsNavigationDrawerExpanded(
|
||||||
(previousIsOpen) => activeItemName !== 'settings' || !previousIsOpen,
|
(previousIsOpen) => activeItemName !== 'settings' || !previousIsOpen,
|
||||||
);
|
);
|
||||||
setCurrentMobileNavigationDrawer('settings');
|
setCurrentMobileNavigationDrawer('settings');
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { Meta, StoryObj } from '@storybook/react';
|
|
||||||
import { useSetRecoilState } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { currentMobileNavigationDrawerState } from '@/navigation/states/currentMobileNavigationDrawerState';
|
import { currentMobileNavigationDrawerState } from '@/navigation/states/currentMobileNavigationDrawerState';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { isNavigationDrawerOpenState } from '@/ui/navigation/states/isNavigationDrawerOpenState';
|
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator';
|
import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator';
|
||||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||||
|
|
||||||
|
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||||
import {
|
import {
|
||||||
AppNavigationDrawer,
|
AppNavigationDrawer,
|
||||||
AppNavigationDrawerProps,
|
AppNavigationDrawerProps,
|
||||||
@ -22,8 +23,8 @@ const MobileNavigationDrawerStateSetterEffect = ({
|
|||||||
mobileNavigationDrawer?: 'main' | 'settings';
|
mobileNavigationDrawer?: 'main' | 'settings';
|
||||||
}) => {
|
}) => {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const setIsNavigationDrawerOpen = useSetRecoilState(
|
const setIsNavigationDrawerExpanded = useSetRecoilState(
|
||||||
isNavigationDrawerOpenState,
|
isNavigationDrawerExpandedState,
|
||||||
);
|
);
|
||||||
const setCurrentMobileNavigationDrawer = useSetRecoilState(
|
const setCurrentMobileNavigationDrawer = useSetRecoilState(
|
||||||
currentMobileNavigationDrawerState,
|
currentMobileNavigationDrawerState,
|
||||||
@ -32,13 +33,13 @@ const MobileNavigationDrawerStateSetterEffect = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isMobile) return;
|
if (!isMobile) return;
|
||||||
|
|
||||||
setIsNavigationDrawerOpen(true);
|
setIsNavigationDrawerExpanded(true);
|
||||||
setCurrentMobileNavigationDrawer(mobileNavigationDrawer);
|
setCurrentMobileNavigationDrawer(mobileNavigationDrawer);
|
||||||
}, [
|
}, [
|
||||||
isMobile,
|
isMobile,
|
||||||
mobileNavigationDrawer,
|
mobileNavigationDrawer,
|
||||||
setCurrentMobileNavigationDrawer,
|
setCurrentMobileNavigationDrawer,
|
||||||
setIsNavigationDrawerOpen,
|
setIsNavigationDrawerExpanded,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -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;
|
||||||
|
};
|
@ -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 { useLastVisitedView } from '@/navigation/hooks/useLastVisitedView';
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
||||||
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
|
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 { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem';
|
||||||
import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection';
|
import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection';
|
||||||
import { getNavigationSubItemState } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemState';
|
import { getNavigationSubItemState } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemState';
|
||||||
import { View } from '@/views/types/View';
|
import { View } from '@/views/types/View';
|
||||||
import { getObjectMetadataItemViews } from '@/views/utils/getObjectMetadataItemViews';
|
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 = [
|
const ORDERED_STANDARD_OBJECTS = [
|
||||||
'person',
|
'person',
|
||||||
'company',
|
'company',
|
||||||
@ -38,107 +38,105 @@ export const NavigationDrawerSectionForObjectMetadataItems = ({
|
|||||||
|
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
const currentPath = useLocation().pathname;
|
const currentPath = useLocation().pathname;
|
||||||
|
|
||||||
const { getLastVisitedViewIdFromObjectMetadataItemId } = useLastVisitedView();
|
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 (
|
||||||
|
<NavigationDrawerItemsCollapsedContainer
|
||||||
|
isGroup={shouldSubItemsBeDisplayed}
|
||||||
|
>
|
||||||
|
<NavigationDrawerItem
|
||||||
|
key={objectMetadataItem.id}
|
||||||
|
label={objectMetadataItem.labelPlural}
|
||||||
|
to={navigationPath}
|
||||||
|
Icon={getIcon(objectMetadataItem.icon)}
|
||||||
|
active={isActive}
|
||||||
|
/>
|
||||||
|
{shouldSubItemsBeDisplayed &&
|
||||||
|
sortedObjectMetadataViews.map((view, index) => (
|
||||||
|
<NavigationDrawerSubItem
|
||||||
|
label={view.name}
|
||||||
|
to={`/objects/${objectMetadataItem.namePlural}?view=${view.id}`}
|
||||||
|
active={viewId === view.id}
|
||||||
|
subItemState={getNavigationSubItemState({
|
||||||
|
index,
|
||||||
|
arrayLength: subItemArrayLength,
|
||||||
|
selectedIndex: selectedSubItemIndex,
|
||||||
|
})}
|
||||||
|
Icon={getIcon(view.icon)}
|
||||||
|
key={view.id}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</NavigationDrawerItemsCollapsedContainer>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
objectMetadataItems.length > 0 && (
|
objectMetadataItems.length > 0 && (
|
||||||
<NavigationDrawerSection>
|
<NavigationDrawerSection>
|
||||||
<NavigationDrawerSectionTitle
|
<NavigationDrawerAnimatedCollapseWrapper>
|
||||||
label={sectionTitle}
|
<NavigationDrawerSectionTitle
|
||||||
onClick={() => toggleNavigationSection()}
|
label={sectionTitle}
|
||||||
/>
|
onClick={() => toggleNavigationSection()}
|
||||||
|
/>
|
||||||
{isNavigationSectionOpen &&
|
</NavigationDrawerAnimatedCollapseWrapper>
|
||||||
[
|
{isNavigationSectionOpen && renderObjectMetadataItems()}
|
||||||
...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 (
|
|
||||||
<div key={objectMetadataItem.id}>
|
|
||||||
<NavigationDrawerItem
|
|
||||||
key={objectMetadataItem.id}
|
|
||||||
label={objectMetadataItem.labelPlural}
|
|
||||||
to={navigationPath}
|
|
||||||
Icon={getIcon(objectMetadataItem.icon)}
|
|
||||||
active={
|
|
||||||
currentPath === `/objects/${objectMetadataItem.namePlural}`
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{shouldSubItemsBeDisplayed &&
|
|
||||||
sortedObjectMetadataViews.map((view, index) => (
|
|
||||||
<NavigationDrawerSubItem
|
|
||||||
label={view.name}
|
|
||||||
to={`/objects/${objectMetadataItem.namePlural}?view=${view.id}`}
|
|
||||||
active={viewId === view.id}
|
|
||||||
subItemState={getNavigationSubItemState({
|
|
||||||
index,
|
|
||||||
arrayLength: subItemArrayLength,
|
|
||||||
selectedIndex: selectedSubItemIndex,
|
|
||||||
})}
|
|
||||||
Icon={getIcon(view.icon)}
|
|
||||||
key={view.id}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</NavigationDrawerSection>
|
</NavigationDrawerSection>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -8,7 +8,7 @@ import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
|
|||||||
import { OBJECT_SETTINGS_WIDTH } from '@/settings/data-model/constants/ObjectSettings';
|
import { OBJECT_SETTINGS_WIDTH } from '@/settings/data-model/constants/ObjectSettings';
|
||||||
import { SignInBackgroundMockPage } from '@/sign-in-background-mock/components/SignInBackgroundMockPage';
|
import { SignInBackgroundMockPage } from '@/sign-in-background-mock/components/SignInBackgroundMockPage';
|
||||||
import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal';
|
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 { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
import { useScreenSize } from '@/ui/utilities/screen-size/hooks/useScreenSize';
|
import { useScreenSize } from '@/ui/utilities/screen-size/hooks/useScreenSize';
|
||||||
import { css, Global, useTheme } from '@emotion/react';
|
import { css, Global, useTheme } from '@emotion/react';
|
||||||
@ -84,7 +84,7 @@ export const DefaultLayout = () => {
|
|||||||
isSettingsPage && !isMobile
|
isSettingsPage && !isMobile
|
||||||
? (windowsWidth -
|
? (windowsWidth -
|
||||||
(OBJECT_SETTINGS_WIDTH +
|
(OBJECT_SETTINGS_WIDTH +
|
||||||
DESKTOP_NAV_DRAWER_WIDTHS.menu +
|
NAV_DRAWER_WIDTHS.menu.desktop.expanded +
|
||||||
64)) /
|
64)) /
|
||||||
2
|
2
|
||||||
: 0,
|
: 0,
|
||||||
|
@ -13,7 +13,8 @@ import {
|
|||||||
|
|
||||||
import { IconButton } from '@/ui/input/button/components/IconButton';
|
import { IconButton } from '@/ui/input/button/components/IconButton';
|
||||||
import { NavigationDrawerCollapseButton } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerCollapseButton';
|
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';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
|
|
||||||
export const PAGE_BAR_MIN_HEIGHT = 40;
|
export const PAGE_BAR_MIN_HEIGHT = 40;
|
||||||
@ -108,12 +109,14 @@ export const PageHeader = ({
|
|||||||
}: PageHeaderProps) => {
|
}: PageHeaderProps) => {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isNavigationDrawerOpen = useRecoilValue(isNavigationDrawerOpenState);
|
const isNavigationDrawerExpanded = useRecoilValue(
|
||||||
|
isNavigationDrawerExpandedState,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledTopBarContainer width={width}>
|
<StyledTopBarContainer width={width}>
|
||||||
<StyledLeftContainer>
|
<StyledLeftContainer>
|
||||||
{!isMobile && !isNavigationDrawerOpen && (
|
{!isMobile && !isNavigationDrawerExpanded && (
|
||||||
<StyledTopBarButtonContainer>
|
<StyledTopBarButtonContainer>
|
||||||
<NavigationDrawerCollapseButton direction="right" />
|
<NavigationDrawerCollapseButton direction="right" />
|
||||||
</StyledTopBarButtonContainer>
|
</StyledTopBarButtonContainer>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { useState } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { IconChevronDown } from 'twenty-ui';
|
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 { useWorkspaceSwitching } from '@/ui/navigation/navigation-drawer/hooks/useWorkspaceSwitching';
|
||||||
import { NavigationDrawerHotKeyScope } from '@/ui/navigation/navigation-drawer/types/NavigationDrawerHotKeyScope';
|
import { NavigationDrawerHotKeyScope } from '@/ui/navigation/navigation-drawer/types/NavigationDrawerHotKeyScope';
|
||||||
import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI';
|
import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI';
|
||||||
|
import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper';
|
||||||
|
|
||||||
const StyledLogo = styled.div<{ logo: string }>`
|
const StyledLogo = styled.div<{ logo: string }>`
|
||||||
background: url(${({ logo }) => logo});
|
background: url(${({ logo }) => logo});
|
||||||
@ -37,8 +38,6 @@ const StyledContainer = styled.div`
|
|||||||
padding: calc(${({ theme }) => theme.spacing(1)} - 1px);
|
padding: calc(${({ theme }) => theme.spacing(1)} - 1px);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
gap: ${({ theme }) => theme.spacing(1)};
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||||
@ -48,6 +47,7 @@ const StyledContainer = styled.div`
|
|||||||
const StyledLabel = styled.div`
|
const StyledLabel = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
margin: 0 ${({ theme }) => theme.spacing(1)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledIconChevronDown = styled(IconChevronDown)<{ disabled?: boolean }>`
|
const StyledIconChevronDown = styled(IconChevronDown)<{ disabled?: boolean }>`
|
||||||
@ -95,11 +95,15 @@ export const MultiWorkspaceDropdownButton = ({
|
|||||||
) ?? ''
|
) ?? ''
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<StyledLabel>{currentWorkspace?.displayName ?? ''}</StyledLabel>
|
<NavigationDrawerAnimatedCollapseWrapper>
|
||||||
<StyledIconChevronDown
|
<StyledLabel>{currentWorkspace?.displayName ?? ''}</StyledLabel>
|
||||||
size={theme.icon.size.md}
|
</NavigationDrawerAnimatedCollapseWrapper>
|
||||||
stroke={theme.icon.stroke.sm}
|
<NavigationDrawerAnimatedCollapseWrapper>
|
||||||
/>
|
<StyledIconChevronDown
|
||||||
|
size={theme.icon.size.md}
|
||||||
|
stroke={theme.icon.stroke.sm}
|
||||||
|
/>
|
||||||
|
</NavigationDrawerAnimatedCollapseWrapper>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
}
|
}
|
||||||
dropdownComponents={
|
dropdownComponents={
|
||||||
|
@ -1,49 +1,45 @@
|
|||||||
import { css, useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { ReactNode, useState } from 'react';
|
import { ReactNode, useState } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { MOBILE_VIEWPORT } from 'twenty-ui';
|
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 { 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 { NavigationDrawerBackButton } from './NavigationDrawerBackButton';
|
||||||
import { NavigationDrawerHeader } from './NavigationDrawerHeader';
|
import { NavigationDrawerHeader } from './NavigationDrawerHeader';
|
||||||
|
import { useIsSettingsDrawer } from '@/navigation/hooks/useIsSettingsDrawer';
|
||||||
|
|
||||||
export type NavigationDrawerProps = {
|
export type NavigationDrawerProps = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
footer?: ReactNode;
|
footer?: ReactNode;
|
||||||
isSubMenu?: boolean;
|
|
||||||
logo?: string;
|
logo?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledAnimatedContainer = styled(motion.div)`
|
const StyledAnimatedContainer = styled(motion.div)``;
|
||||||
display: flex;
|
|
||||||
justify-content: end;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledContainer = styled.div<{ isSubMenu?: boolean }>`
|
const StyledContainer = styled.div<{
|
||||||
|
isSettings?: boolean;
|
||||||
|
isMobile?: boolean;
|
||||||
|
}>`
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
width: ${NAV_DRAWER_WIDTHS.menu.desktop.expanded}px;
|
||||||
gap: ${({ theme }) => theme.spacing(3)};
|
gap: ${({ theme }) => theme.spacing(3)};
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-width: ${DESKTOP_NAV_DRAWER_WIDTHS.menu}px;
|
padding: ${({ theme, isSettings, isMobile }) =>
|
||||||
padding: ${({ theme }) => theme.spacing(3, 2, 4)};
|
isSettings
|
||||||
|
? isMobile
|
||||||
${({ isSubMenu, theme }) =>
|
? theme.spacing(3, 8)
|
||||||
isSubMenu
|
: theme.spacing(3, 8, 4, 0)
|
||||||
? css`
|
: theme.spacing(3, 2, 4)};
|
||||||
padding-left: ${theme.spacing(0)};
|
|
||||||
padding-right: ${theme.spacing(8)};
|
|
||||||
`
|
|
||||||
: ''}
|
|
||||||
|
|
||||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -61,15 +57,16 @@ export const NavigationDrawer = ({
|
|||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
footer,
|
footer,
|
||||||
isSubMenu,
|
|
||||||
logo,
|
logo,
|
||||||
title,
|
title,
|
||||||
}: NavigationDrawerProps) => {
|
}: NavigationDrawerProps) => {
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
const isSettingsDrawer = useIsSettingsDrawer();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isNavigationDrawerOpen = useRecoilValue(isNavigationDrawerOpenState);
|
const isNavigationDrawerExpanded = useRecoilValue(
|
||||||
const isSettingsPage = useIsSettingsPage();
|
isNavigationDrawerExpandedState,
|
||||||
|
);
|
||||||
|
|
||||||
const handleHover = () => {
|
const handleHover = () => {
|
||||||
setIsHovered(true);
|
setIsHovered(true);
|
||||||
@ -79,30 +76,35 @@ export const NavigationDrawer = ({
|
|||||||
setIsHovered(false);
|
setIsHovered(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const desktopWidth = !isNavigationDrawerOpen
|
const desktopWidth = isNavigationDrawerExpanded
|
||||||
? 12
|
? NAV_DRAWER_WIDTHS.menu.desktop.expanded
|
||||||
: DESKTOP_NAV_DRAWER_WIDTHS.menu;
|
: 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 (
|
return (
|
||||||
<StyledAnimatedContainer
|
<StyledAnimatedContainer
|
||||||
className={className}
|
className={className}
|
||||||
initial={false}
|
initial={false}
|
||||||
animate={{
|
animate={navigationDrawerAnimate}
|
||||||
width: isMobile ? mobileWidth : desktopWidth,
|
|
||||||
opacity: isNavigationDrawerOpen || isSettingsPage ? 1 : 0,
|
|
||||||
}}
|
|
||||||
transition={{
|
transition={{
|
||||||
duration: theme.animation.duration.normal,
|
duration: theme.animation.duration.normal,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<StyledContainer
|
<StyledContainer
|
||||||
isSubMenu={isSubMenu}
|
isSettings={isSettingsDrawer}
|
||||||
|
isMobile={isMobile}
|
||||||
onMouseEnter={handleHover}
|
onMouseEnter={handleHover}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
>
|
>
|
||||||
{isSubMenu && title ? (
|
{isSettingsDrawer && title ? (
|
||||||
!isMobile && <NavigationDrawerBackButton title={title} />
|
!isMobile && <NavigationDrawerBackButton title={title} />
|
||||||
) : (
|
) : (
|
||||||
<NavigationDrawerHeader
|
<NavigationDrawerHeader
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { AnimationControls, motion, TargetAndTransition } from 'framer-motion';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||||
|
import { useTheme } from '@emotion/react';
|
||||||
|
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
|
||||||
|
|
||||||
|
const StyledAnimatedContainer = styled(motion.div)``;
|
||||||
|
|
||||||
|
export const NavigationDrawerAnimatedCollapseWrapper = ({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) => {
|
||||||
|
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 (
|
||||||
|
<StyledAnimatedContainer
|
||||||
|
initial={false}
|
||||||
|
animate={animate}
|
||||||
|
transition={{
|
||||||
|
duration: theme.animation.duration.normal,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</StyledAnimatedContainer>
|
||||||
|
);
|
||||||
|
};
|
@ -1,9 +1,11 @@
|
|||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
import { IconX } from 'twenty-ui';
|
import { IconX } from 'twenty-ui';
|
||||||
|
|
||||||
import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink';
|
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';
|
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
||||||
|
|
||||||
type NavigationDrawerBackButtonProps = {
|
type NavigationDrawerBackButtonProps = {
|
||||||
@ -43,9 +45,22 @@ export const NavigationDrawerBackButton = ({
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const navigationMemorizedUrl = useRecoilValue(navigationMemorizedUrlState);
|
const navigationMemorizedUrl = useRecoilValue(navigationMemorizedUrlState);
|
||||||
|
|
||||||
|
const setIsNavigationDrawerExpanded = useSetRecoilState(
|
||||||
|
isNavigationDrawerExpandedState,
|
||||||
|
);
|
||||||
|
const navigationDrawerExpandedMemorized = useRecoilValue(
|
||||||
|
navigationDrawerExpandedMemorizedState,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<UndecoratedLink to={navigationMemorizedUrl} replace>
|
<UndecoratedLink
|
||||||
|
to={navigationMemorizedUrl}
|
||||||
|
replace
|
||||||
|
onClick={() =>
|
||||||
|
setIsNavigationDrawerExpanded(navigationDrawerExpandedMemorized)
|
||||||
|
}
|
||||||
|
>
|
||||||
<StyledIconAndButtonContainer>
|
<StyledIconAndButtonContainer>
|
||||||
<IconX
|
<IconX
|
||||||
size={theme.icon.size.md}
|
size={theme.icon.size.md}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { IconButton } from '@/ui/input/button/components/IconButton';
|
||||||
|
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useSetRecoilState } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
import {
|
import {
|
||||||
@ -5,9 +7,6 @@ import {
|
|||||||
IconLayoutSidebarRightCollapse,
|
IconLayoutSidebarRightCollapse,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
|
|
||||||
import { IconButton } from '@/ui/input/button/components/IconButton';
|
|
||||||
import { isNavigationDrawerOpenState } from '@/ui/navigation/states/isNavigationDrawerOpenState';
|
|
||||||
|
|
||||||
const StyledCollapseButton = styled.div`
|
const StyledCollapseButton = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||||
@ -33,15 +32,17 @@ export const NavigationDrawerCollapseButton = ({
|
|||||||
className,
|
className,
|
||||||
direction = 'left',
|
direction = 'left',
|
||||||
}: NavigationDrawerCollapseButtonProps) => {
|
}: NavigationDrawerCollapseButtonProps) => {
|
||||||
const setIsNavigationDrawerOpen = useSetRecoilState(
|
const setIsNavigationDrawerExpanded = useSetRecoilState(
|
||||||
isNavigationDrawerOpenState,
|
isNavigationDrawerExpandedState,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledCollapseButton
|
<StyledCollapseButton
|
||||||
className={className}
|
className={className}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setIsNavigationDrawerOpen((previousIsOpen) => !previousIsOpen)
|
setIsNavigationDrawerExpanded(
|
||||||
|
(previousIsExpanded) => !previousIsExpanded,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
@ -7,17 +7,20 @@ import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/consta
|
|||||||
import { DEFAULT_WORKSPACE_NAME } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceName';
|
import { DEFAULT_WORKSPACE_NAME } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceName';
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
|
|
||||||
|
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||||
import { isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
import { NavigationDrawerCollapseButton } from './NavigationDrawerCollapseButton';
|
import { NavigationDrawerCollapseButton } from './NavigationDrawerCollapseButton';
|
||||||
|
import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper';
|
||||||
|
|
||||||
const StyledContainer = styled.div<{ isMultiWorkspace: boolean }>`
|
const StyledContainer = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: ${({ theme, isMultiWorkspace }) =>
|
|
||||||
!isMultiWorkspace ? theme.spacing(2) : null};
|
|
||||||
height: ${({ theme }) => theme.spacing(8)};
|
height: ${({ theme }) => theme.spacing(8)};
|
||||||
user-select: none;
|
user-select: none;
|
||||||
`;
|
`;
|
||||||
|
const StyledSingleWorkspaceContainer = styled(StyledContainer)`
|
||||||
|
gap: ${({ theme }) => theme.spacing(2)};
|
||||||
|
`;
|
||||||
|
|
||||||
const StyledLogo = styled.div<{ logo: string }>`
|
const StyledLogo = styled.div<{ logo: string }>`
|
||||||
background: url(${({ logo }) => logo});
|
background: url(${({ logo }) => logo});
|
||||||
@ -57,21 +60,25 @@ export const NavigationDrawerHeader = ({
|
|||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const workspaces = useRecoilValue(workspacesState);
|
const workspaces = useRecoilValue(workspacesState);
|
||||||
const isMultiWorkspace = workspaces !== null && workspaces.length > 1;
|
const isMultiWorkspace = workspaces !== null && workspaces.length > 1;
|
||||||
|
const isNavigationDrawerExpanded = useRecoilValue(
|
||||||
|
isNavigationDrawerExpandedState,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer isMultiWorkspace={isMultiWorkspace}>
|
<StyledContainer>
|
||||||
{isMultiWorkspace ? (
|
{isMultiWorkspace ? (
|
||||||
<MultiWorkspaceDropdownButton workspaces={workspaces} />
|
<MultiWorkspaceDropdownButton workspaces={workspaces} />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<StyledSingleWorkspaceContainer>
|
||||||
<StyledLogo
|
<StyledLogo
|
||||||
logo={isNonEmptyString(logo) ? logo : DEFAULT_WORKSPACE_LOGO}
|
logo={isNonEmptyString(logo) ? logo : DEFAULT_WORKSPACE_LOGO}
|
||||||
/>
|
/>
|
||||||
<StyledName>{name}</StyledName>
|
<NavigationDrawerAnimatedCollapseWrapper>
|
||||||
</>
|
<StyledName>{name}</StyledName>
|
||||||
|
</NavigationDrawerAnimatedCollapseWrapper>
|
||||||
|
</StyledSingleWorkspaceContainer>
|
||||||
)}
|
)}
|
||||||
|
{!isMobile && isNavigationDrawerExpanded && (
|
||||||
{!isMobile && (
|
|
||||||
<StyledNavigationDrawerCollapseButton
|
<StyledNavigationDrawerCollapseButton
|
||||||
direction="left"
|
direction="left"
|
||||||
show={showCollapseButton}
|
show={showCollapseButton}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import { NavigationDrawerItemBreadcrumb } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemBreadcrumb';
|
import { NavigationDrawerItemBreadcrumb } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemBreadcrumb';
|
||||||
import { NavigationDrawerSubItemState } from '@/ui/navigation/navigation-drawer/types/NavigationDrawerSubItemState';
|
import { NavigationDrawerSubItemState } from '@/ui/navigation/navigation-drawer/types/NavigationDrawerSubItemState';
|
||||||
import { isNavigationDrawerOpenState } from '@/ui/navigation/states/isNavigationDrawerOpenState';
|
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
|
import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper';
|
||||||
import isPropValid from '@emotion/is-prop-valid';
|
import isPropValid from '@emotion/is-prop-valid';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { useSetRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
import {
|
import {
|
||||||
IconComponent,
|
IconComponent,
|
||||||
MOBILE_VIEWPORT,
|
MOBILE_VIEWPORT,
|
||||||
@ -15,6 +16,8 @@ import {
|
|||||||
TablerIconsProps,
|
TablerIconsProps,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
|
||||||
|
import { NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/NavDrawerWidths';
|
||||||
|
|
||||||
const DEFAULT_INDENTATION_LEVEL = 1;
|
const DEFAULT_INDENTATION_LEVEL = 1;
|
||||||
|
|
||||||
@ -38,7 +41,7 @@ export type NavigationDrawerItemProps = {
|
|||||||
type StyledItemProps = Pick<
|
type StyledItemProps = Pick<
|
||||||
NavigationDrawerItemProps,
|
NavigationDrawerItemProps,
|
||||||
'active' | 'danger' | 'indentationLevel' | 'soon' | 'to'
|
'active' | 'danger' | 'indentationLevel' | 'soon' | 'to'
|
||||||
>;
|
> & { isNavigationDrawerExpanded: boolean };
|
||||||
|
|
||||||
const StyledItem = styled('div', {
|
const StyledItem = styled('div', {
|
||||||
shouldForwardProp: (prop) =>
|
shouldForwardProp: (prop) =>
|
||||||
@ -65,9 +68,8 @@ const StyledItem = styled('div', {
|
|||||||
}};
|
}};
|
||||||
cursor: ${(props) => (props.soon ? 'default' : 'pointer')};
|
cursor: ${(props) => (props.soon ? 'default' : 'pointer')};
|
||||||
display: flex;
|
display: flex;
|
||||||
font-family: 'Inter';
|
font-family: ${({ theme }) => theme.font.family};
|
||||||
font-size: ${({ theme }) => theme.font.size.md};
|
font-size: ${({ theme }) => theme.font.size.md};
|
||||||
gap: ${({ theme }) => theme.spacing(2)};
|
|
||||||
|
|
||||||
padding-bottom: ${({ theme }) => theme.spacing(1)};
|
padding-bottom: ${({ theme }) => theme.spacing(1)};
|
||||||
padding-left: ${({ theme }) => theme.spacing(1)};
|
padding-left: ${({ theme }) => theme.spacing(1)};
|
||||||
@ -78,7 +80,12 @@ const StyledItem = styled('div', {
|
|||||||
indentationLevel === 2 ? '2px' : '0'};
|
indentationLevel === 2 ? '2px' : '0'};
|
||||||
|
|
||||||
pointer-events: ${(props) => (props.soon ? 'none' : 'auto')};
|
pointer-events: ${(props) => (props.soon ? 'none' : 'auto')};
|
||||||
width: 100%;
|
|
||||||
|
width: ${(props) =>
|
||||||
|
!props.isNavigationDrawerExpanded
|
||||||
|
? `${NAV_DRAWER_WIDTHS.menu.desktop.collapsed - 24}px`
|
||||||
|
: '100%'};
|
||||||
|
|
||||||
:hover {
|
:hover {
|
||||||
background: ${({ theme }) => theme.background.transparent.light};
|
background: ${({ theme }) => theme.background.transparent.light};
|
||||||
color: ${(props) =>
|
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`
|
const StyledItemLabel = styled.div`
|
||||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
`;
|
`;
|
||||||
@ -111,7 +123,6 @@ const StyledItemCount = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
font-size: ${({ theme }) => theme.font.size.xs};
|
font-size: ${({ theme }) => theme.font.size.xs};
|
||||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||||
|
|
||||||
height: 16px;
|
height: 16px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
@ -151,16 +162,15 @@ export const NavigationDrawerItem = ({
|
|||||||
}: NavigationDrawerItemProps) => {
|
}: NavigationDrawerItemProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
const isSettingsPage = useIsSettingsPage();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const setIsNavigationDrawerOpen = useSetRecoilState(
|
const [isNavigationDrawerExpanded, setIsNavigationDrawerExpanded] =
|
||||||
isNavigationDrawerOpenState,
|
useRecoilState(isNavigationDrawerExpandedState);
|
||||||
);
|
|
||||||
|
|
||||||
const showBreadcrumb = indentationLevel === 2;
|
const showBreadcrumb = indentationLevel === 2;
|
||||||
|
|
||||||
const handleItemClick = () => {
|
const handleItemClick = () => {
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
setIsNavigationDrawerOpen(false);
|
setIsNavigationDrawerExpanded(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDefined(onClick)) {
|
if (isDefined(onClick)) {
|
||||||
@ -185,25 +195,51 @@ export const NavigationDrawerItem = ({
|
|||||||
as={to ? Link : 'div'}
|
as={to ? Link : 'div'}
|
||||||
to={to ? to : undefined}
|
to={to ? to : undefined}
|
||||||
indentationLevel={indentationLevel}
|
indentationLevel={indentationLevel}
|
||||||
|
isNavigationDrawerExpanded={isNavigationDrawerExpanded}
|
||||||
>
|
>
|
||||||
{showBreadcrumb && (
|
{showBreadcrumb && (
|
||||||
<NavigationDrawerItemBreadcrumb state={subItemState} />
|
<NavigationDrawerAnimatedCollapseWrapper>
|
||||||
)}
|
<NavigationDrawerItemBreadcrumb state={subItemState} />
|
||||||
{Icon && (
|
</NavigationDrawerAnimatedCollapseWrapper>
|
||||||
<Icon
|
|
||||||
style={{ minWidth: theme.icon.size.md }}
|
|
||||||
size={theme.icon.size.md}
|
|
||||||
stroke={theme.icon.stroke.md}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<StyledItemLabel>{label}</StyledItemLabel>
|
|
||||||
{soon && <Pill label="Soon" />}
|
|
||||||
{!!count && <StyledItemCount>{count}</StyledItemCount>}
|
|
||||||
{keyboard && (
|
|
||||||
<StyledKeyBoardShortcut className="keyboard-shortcuts">
|
|
||||||
{keyboard}
|
|
||||||
</StyledKeyBoardShortcut>
|
|
||||||
)}
|
)}
|
||||||
|
<StyledItemElementsContainer>
|
||||||
|
{Icon && (
|
||||||
|
<Icon
|
||||||
|
style={{ minWidth: theme.icon.size.md }}
|
||||||
|
size={theme.icon.size.md}
|
||||||
|
stroke={theme.icon.stroke.md}
|
||||||
|
color={
|
||||||
|
showBreadcrumb && !isSettingsPage && !isNavigationDrawerExpanded
|
||||||
|
? theme.font.color.light
|
||||||
|
: 'currentColor'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<NavigationDrawerAnimatedCollapseWrapper>
|
||||||
|
<StyledItemLabel>{label}</StyledItemLabel>
|
||||||
|
</NavigationDrawerAnimatedCollapseWrapper>
|
||||||
|
|
||||||
|
{soon && (
|
||||||
|
<NavigationDrawerAnimatedCollapseWrapper>
|
||||||
|
<Pill label="Soon" />
|
||||||
|
</NavigationDrawerAnimatedCollapseWrapper>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!!count && (
|
||||||
|
<NavigationDrawerAnimatedCollapseWrapper>
|
||||||
|
<StyledItemCount>{count}</StyledItemCount>
|
||||||
|
</NavigationDrawerAnimatedCollapseWrapper>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{keyboard && (
|
||||||
|
<NavigationDrawerAnimatedCollapseWrapper>
|
||||||
|
<StyledKeyBoardShortcut className="keyboard-shortcuts">
|
||||||
|
{keyboard}
|
||||||
|
</StyledKeyBoardShortcut>
|
||||||
|
</NavigationDrawerAnimatedCollapseWrapper>
|
||||||
|
)}
|
||||||
|
</StyledItemElementsContainer>
|
||||||
</StyledItem>
|
</StyledItem>
|
||||||
</StyledNavigationDrawerItemContainer>
|
</StyledNavigationDrawerItemContainer>
|
||||||
);
|
);
|
||||||
|
@ -6,9 +6,10 @@ export type NavigationDrawerItemBreadcrumbProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const StyledNavigationDrawerItemBreadcrumbContainer = styled.div`
|
const StyledNavigationDrawerItemBreadcrumbContainer = styled.div`
|
||||||
margin-left: 7.5px;
|
|
||||||
|
|
||||||
height: 28px;
|
height: 28px;
|
||||||
|
|
||||||
|
margin-left: 7.5px;
|
||||||
|
margin-right: ${({ theme }) => theme.spacing(2)};
|
||||||
width: 9px;
|
width: 9px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -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 (
|
||||||
|
<StyledAnimationGroupContainer
|
||||||
|
initial={false}
|
||||||
|
animate={animate}
|
||||||
|
transition={{ duration: theme.animation.duration.normal }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</StyledAnimationGroupContainer>
|
||||||
|
);
|
||||||
|
};
|
@ -1,8 +1,10 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { currentUserState } from '@/auth/states/currentUserState';
|
import { currentUserState } from '@/auth/states/currentUserState';
|
||||||
|
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
|
||||||
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
|
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
|
||||||
import { NavigationDrawerSectionTitleSkeletonLoader } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitleSkeletonLoader';
|
import { NavigationDrawerSectionTitleSkeletonLoader } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitleSkeletonLoader';
|
||||||
|
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { isDefined } from 'twenty-ui';
|
import { isDefined } from 'twenty-ui';
|
||||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||||
@ -37,9 +39,22 @@ export const NavigationDrawerSectionTitle = ({
|
|||||||
}: NavigationDrawerSectionTitleProps) => {
|
}: NavigationDrawerSectionTitleProps) => {
|
||||||
const currentUser = useRecoilValue(currentUserState);
|
const currentUser = useRecoilValue(currentUserState);
|
||||||
const loading = useIsPrefetchLoading();
|
const loading = useIsPrefetchLoading();
|
||||||
|
const isNavigationDrawerExpanded = useRecoilValue(
|
||||||
|
isNavigationDrawerExpandedState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isSettingsPage = useIsSettingsPage();
|
||||||
|
|
||||||
if (loading && isDefined(currentUser)) {
|
if (loading && isDefined(currentUser)) {
|
||||||
return <NavigationDrawerSectionTitleSkeletonLoader />;
|
return <NavigationDrawerSectionTitleSkeletonLoader />;
|
||||||
}
|
}
|
||||||
return <StyledTitle onClick={onClick}>{label}</StyledTitle>;
|
return (
|
||||||
|
<StyledTitle
|
||||||
|
onClick={
|
||||||
|
isNavigationDrawerExpanded || isSettingsPage ? onClick : undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</StyledTitle>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@ -53,6 +53,11 @@ export const Default: Story = {
|
|||||||
Icon={IconBell}
|
Icon={IconBell}
|
||||||
soon={true}
|
soon={true}
|
||||||
/>
|
/>
|
||||||
|
<NavigationDrawerItem
|
||||||
|
label="Search"
|
||||||
|
Icon={IconSearch}
|
||||||
|
keyboard={['⌘', 'K']}
|
||||||
|
/>
|
||||||
<NavigationDrawerItem
|
<NavigationDrawerItem
|
||||||
label="Settings"
|
label="Settings"
|
||||||
to="/settings/profile"
|
to="/settings/profile"
|
||||||
@ -84,9 +89,8 @@ export const Default: Story = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Submenu: Story = {
|
export const Settings: Story = {
|
||||||
args: {
|
args: {
|
||||||
isSubMenu: true,
|
|
||||||
title: 'Settings',
|
title: 'Settings',
|
||||||
children: (
|
children: (
|
||||||
<>
|
<>
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
export const DESKTOP_NAV_DRAWER_WIDTHS = {
|
|
||||||
menu: 220,
|
|
||||||
};
|
|
@ -0,0 +1,12 @@
|
|||||||
|
export const NAV_DRAWER_WIDTHS = {
|
||||||
|
menu: {
|
||||||
|
mobile: {
|
||||||
|
collapsed: 0,
|
||||||
|
expanded: '100%',
|
||||||
|
},
|
||||||
|
desktop: {
|
||||||
|
collapsed: 40,
|
||||||
|
expanded: 220,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
@ -3,7 +3,7 @@ import { MOBILE_VIEWPORT } from 'twenty-ui';
|
|||||||
|
|
||||||
const isMobile = window.innerWidth <= MOBILE_VIEWPORT;
|
const isMobile = window.innerWidth <= MOBILE_VIEWPORT;
|
||||||
|
|
||||||
export const isNavigationDrawerOpenState = atom({
|
export const isNavigationDrawerExpandedState = atom({
|
||||||
key: 'isNavigationDrawerOpen',
|
key: 'isNavigationDrawerExpanded',
|
||||||
default: !isMobile,
|
default: !isMobile,
|
||||||
});
|
});
|
@ -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,
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user