Navbar with AI chats (#18161)

## Summary
Add Home/Chat tabs and a dedicated threads list in the navigation
drawer.

## Changes
- **Navbar tabs:** Tabs in the drawer to switch between Home and Chat
(with “New chat” button). Shown on desktop when expanded and on mobile
below the workspace selector.
- **Navbar threads list:** New `NavigationDrawerAIChatThreadsList` for
the Chat tab with date groups (Today / Yesterday / Older), thread rows
as `NavigationDrawerItem` (IconComment, title, timestamp). Shared
`useAIChatThreadClick` hook used by navbar and command menu; navbar
passes `resetNavigationStack: true`.
- **NavigationDrawerItem:** New `alwaysShowRightOptions` prop so the
timestamp is always visible (no hover-only).

---------

Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Abdul Rahman
2026-02-28 04:07:14 +05:30
committed by GitHub
parent 9f5a8735c9
commit e806d36099
34 changed files with 970 additions and 291 deletions
@@ -1,51 +1,19 @@
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import styled from '@emotion/styled';
import { useFavoritesByFolder } from '@/favorites/hooks/useFavoritesByFolder';
import { NavigationMenuItemFolderContentDispatcherEffect } from '@/navigation-menu-item/components/NavigationMenuItemFolderContentDispatcher';
import { useNavigationMenuItemsByFolder } from '@/navigation-menu-item/hooks/useNavigationMenuItemsByFolder';
import { MainNavigationDrawerFixedItems } from '@/navigation/components/MainNavigationDrawerFixedItems';
import { MainNavigationDrawerScrollableItems } from '@/navigation/components/MainNavigationDrawerScrollableItems';
import { MainNavigationDrawerAIChatContent } from '@/navigation/components/MainNavigationDrawerAIChatContent';
import { MainNavigationDrawerNavigationContent } from '@/navigation/components/MainNavigationDrawerNavigationContent';
import { MainNavigationDrawerTabsRow } from '@/navigation/components/MainNavigationDrawerTabsRow';
import { NavigationDrawer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawer';
import { NavigationDrawerFixedContent } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerFixedContent';
import { NavigationDrawerScrollableContent } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerScrollableContent';
import { currentFavoriteFolderIdState } from '@/ui/navigation/navigation-drawer/states/currentFavoriteFolderIdState';
import { currentNavigationMenuItemFolderIdState } from '@/ui/navigation/navigation-drawer/states/currentNavigationMenuItemFolderIdState';
import { navigationDrawerActiveTabState } from '@/ui/navigation/states/navigationDrawerActiveTabState';
import { NAVIGATION_DRAWER_TABS } from '@/ui/navigation/states/navigationDrawerTabs';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
const StyledScrollableContent = styled.div`
height: 100%;
min-height: 0;
`;
export const MainNavigationDrawer = ({ className }: { className?: string }) => {
const navigationDrawerActiveTab = useAtomStateValue(
navigationDrawerActiveTabState,
);
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
const currentFavoriteFolderId = useAtomStateValue(
currentFavoriteFolderIdState,
);
const currentNavigationMenuItemFolderId = useAtomStateValue(
currentNavigationMenuItemFolderIdState,
);
const { favoritesByFolder } = useFavoritesByFolder();
const { navigationMenuItemsByFolder } = useNavigationMenuItemsByFolder();
const isNavigationMenuItemEditingEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED,
);
const openedFavoriteFolder = favoritesByFolder.find(
(f) => f.folderId === currentFavoriteFolderId,
);
const openedNavigationMenuItemFolder = navigationMenuItemsByFolder.find(
(f) => f.id === currentNavigationMenuItemFolderId,
);
const openedFolder = isNavigationMenuItemEditingEnabled
? openedNavigationMenuItemFolder
: openedFavoriteFolder;
const openedFolderId = openedNavigationMenuItemFolder?.id ?? '';
return (
<NavigationDrawer
@@ -53,36 +21,15 @@ export const MainNavigationDrawer = ({ className }: { className?: string }) => {
title={currentWorkspace?.displayName ?? ''}
>
<NavigationDrawerFixedContent>
<MainNavigationDrawerFixedItems />
<MainNavigationDrawerTabsRow />
</NavigationDrawerFixedContent>
<NavigationDrawerScrollableContent>
{isNavigationMenuItemEditingEnabled ? (
<StyledScrollableContent>
{openedFolder ? (
<NavigationMenuItemFolderContentDispatcherEffect
folderName={openedFolder.folderName}
folderId={openedFolderId}
favorites={openedFavoriteFolder?.favorites}
navigationMenuItems={
openedNavigationMenuItemFolder?.navigationMenuItems
}
/>
) : (
<MainNavigationDrawerScrollableItems />
)}
</StyledScrollableContent>
) : openedFolder ? (
<NavigationMenuItemFolderContentDispatcherEffect
folderName={openedFolder.folderName}
folderId={openedFolderId}
favorites={openedFavoriteFolder?.favorites}
navigationMenuItems={
openedNavigationMenuItemFolder?.navigationMenuItems
}
/>
{navigationDrawerActiveTab ===
NAVIGATION_DRAWER_TABS.AI_CHAT_HISTORY ? (
<MainNavigationDrawerAIChatContent />
) : (
<MainNavigationDrawerScrollableItems />
<MainNavigationDrawerNavigationContent />
)}
</NavigationDrawerScrollableContent>
</NavigationDrawer>
@@ -0,0 +1,18 @@
import styled from '@emotion/styled';
import { NavigationDrawerAIChatThreadsList } from '@/ai/components/NavigationDrawerAIChatThreadsList';
const StyledAIChatThreadsListWrapper = styled.div`
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
`;
export const MainNavigationDrawerAIChatContent = () => {
return (
<StyledAIChatThreadsListWrapper>
<NavigationDrawerAIChatThreadsList />
</StyledAIChatThreadsListWrapper>
);
};
@@ -1,72 +0,0 @@
import { useOpenAskAIPageInCommandMenu } from '@/command-menu/hooks/useOpenAskAIPageInCommandMenu';
import { useOpenRecordsSearchPageInCommandMenu } from '@/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu';
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { navigationDrawerExpandedMemorizedState } from '@/ui/navigation/states/navigationDrawerExpandedMemorizedState';
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useLingui } from '@lingui/react/macro';
import { useLocation, useNavigate } from 'react-router-dom';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import { IconSearch, IconSettings, IconSparkles } from 'twenty-ui/display';
import { useIsMobile } from 'twenty-ui/utilities';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
export const MainNavigationDrawerFixedItems = () => {
const isMobile = useIsMobile();
const location = useLocation();
const setNavigationMemorizedUrl = useSetAtomState(
navigationMemorizedUrlState,
);
const [isNavigationDrawerExpanded, setIsNavigationDrawerExpanded] =
useAtomState(isNavigationDrawerExpandedState);
const setNavigationDrawerExpandedMemorized = useSetAtomState(
navigationDrawerExpandedMemorizedState,
);
const navigate = useNavigate();
const { t } = useLingui();
const { openRecordsSearchPage } = useOpenRecordsSearchPageInCommandMenu();
const { openAskAIPage } = useOpenAskAIPageInCommandMenu();
const isAiEnabled = useIsFeatureEnabled(FeatureFlagKey.IS_AI_ENABLED);
return (
!isMobile && (
<>
<NavigationDrawerItem
label={t`Search`}
Icon={IconSearch}
onClick={openRecordsSearchPage}
keyboard={['/']}
mouseUpNavigation={true}
/>
{isAiEnabled && (
<NavigationDrawerItem
label={t`Ask AI`}
Icon={IconSparkles}
onClick={() => openAskAIPage({ resetNavigationStack: true })}
keyboard={['@']}
mouseUpNavigation={true}
/>
)}
<NavigationDrawerItem
label={t`Settings`}
to={getSettingsPath(SettingsPath.ProfilePage)}
onClick={() => {
setNavigationDrawerExpandedMemorized(isNavigationDrawerExpanded);
setIsNavigationDrawerExpanded(true);
setNavigationMemorizedUrl(location.pathname + location.search);
navigate(getSettingsPath(SettingsPath.ProfilePage));
}}
Icon={IconSettings}
/>
</>
)
);
};
@@ -0,0 +1,74 @@
import styled from '@emotion/styled';
import { useFavoritesByFolder } from '@/favorites/hooks/useFavoritesByFolder';
import { NavigationMenuItemFolderContentDispatcherEffect } from '@/navigation-menu-item/components/NavigationMenuItemFolderContentDispatcher';
import { useNavigationMenuItemsByFolder } from '@/navigation-menu-item/hooks/useNavigationMenuItemsByFolder';
import { MainNavigationDrawerScrollableItems } from '@/navigation/components/MainNavigationDrawerScrollableItems';
import { currentFavoriteFolderIdState } from '@/ui/navigation/navigation-drawer/states/currentFavoriteFolderIdState';
import { currentNavigationMenuItemFolderIdState } from '@/ui/navigation/navigation-drawer/states/currentNavigationMenuItemFolderIdState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
const StyledScrollableContent = styled.div`
height: 100%;
min-height: 0;
`;
export const MainNavigationDrawerNavigationContent = () => {
const currentFavoriteFolderId = useAtomStateValue(
currentFavoriteFolderIdState,
);
const currentNavigationMenuItemFolderId = useAtomStateValue(
currentNavigationMenuItemFolderIdState,
);
const { favoritesByFolder } = useFavoritesByFolder();
const { navigationMenuItemsByFolder } = useNavigationMenuItemsByFolder();
const isNavigationMenuItemEditingEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED,
);
const openedFavoriteFolder = favoritesByFolder.find(
(folder) => folder.folderId === currentFavoriteFolderId,
);
const openedNavigationMenuItemFolder = navigationMenuItemsByFolder.find(
(folder) => folder.id === currentNavigationMenuItemFolderId,
);
const openedFolder = isNavigationMenuItemEditingEnabled
? openedNavigationMenuItemFolder
: openedFavoriteFolder;
const openedFolderId = openedNavigationMenuItemFolder?.id ?? '';
if (isNavigationMenuItemEditingEnabled) {
return (
<StyledScrollableContent>
{openedFolder ? (
<NavigationMenuItemFolderContentDispatcherEffect
folderName={openedFolder.folderName}
folderId={openedFolderId}
favorites={openedFavoriteFolder?.favorites}
navigationMenuItems={
openedNavigationMenuItemFolder?.navigationMenuItems
}
/>
) : (
<MainNavigationDrawerScrollableItems />
)}
</StyledScrollableContent>
);
}
return openedFolder ? (
<NavigationMenuItemFolderContentDispatcherEffect
folderName={openedFolder.folderName}
folderId={openedFolderId}
favorites={openedFavoriteFolder?.favorites}
navigationMenuItems={openedNavigationMenuItemFolder?.navigationMenuItems}
/>
) : (
<MainNavigationDrawerScrollableItems />
);
};
@@ -3,6 +3,8 @@ import { RemoteNavigationDrawerSection } from '@/object-metadata/components/Remo
import styled from '@emotion/styled';
import { lazy, Suspense } from 'react';
import { NavigationDrawerOtherSection } from '@/navigation/components/NavigationDrawerOtherSection';
const CurrentWorkspaceMemberNavigationMenuItemFoldersDispatcher = lazy(() =>
import(
'@/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItemFoldersDispatcher'
@@ -36,6 +38,7 @@ export const MainNavigationDrawerScrollableItems = () => {
<WorkspaceNavigationMenuItemsDispatcher />
</Suspense>
<RemoteNavigationDrawerSection />
<NavigationDrawerOtherSection />
</StyledScrollableItemsContainer>
);
};
@@ -0,0 +1,247 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import {
IconComment,
IconHome,
IconMessageCirclePlus,
} from 'twenty-ui/display';
import { useIsMobile } from 'twenty-ui/utilities';
import { useCreateNewAIChatThread } from '@/ai/hooks/useCreateNewAIChatThread';
import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { navigationDrawerActiveTabState } from '@/ui/navigation/states/navigationDrawerActiveTabState';
import {
type NavigationDrawerActiveTab,
NAVIGATION_DRAWER_TABS,
} from '@/ui/navigation/states/navigationDrawerTabs';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
const StyledRow = styled.div<{ isExpanded: boolean }>`
align-items: center;
display: flex;
justify-content: ${({ isExpanded }) =>
isExpanded ? 'space-between' : 'center'};
gap: ${({ theme, isExpanded }) => (isExpanded ? theme.spacing(2) : 0)};
width: 100%;
transition: gap ${({ theme }) => theme.animation.duration.normal}s ease;
`;
const StyledTabsPill = styled.div`
align-items: center;
border: 1px solid ${({ theme }) => theme.border.color.medium};
background: ${({ theme }) => theme.background.secondary};
border-radius: ${({ theme }) => theme.border.radius.pill};
padding: ${({ theme }) => theme.spacing(0.75)};
height: ${({ theme }) => theme.spacing(7)};
display: flex;
width: ${({ theme }) => theme.spacing(18)};
gap: ${({ theme }) => theme.spacing(0.5)};
box-sizing: border-box;
`;
const StyledTabWrapper = styled.div<{ isActive: boolean }>`
border-radius: ${({ theme }) => theme.border.radius.pill};
align-items: center;
background: ${({ theme, isActive }) =>
isActive ? theme.background.transparent.light : 'transparent'};
color: ${({ theme, isActive }) =>
isActive ? theme.font.color.primary : theme.font.color.tertiary};
cursor: pointer;
display: flex;
justify-content: center;
height: 100%;
flex: 1;
&:hover {
background: ${({ theme, isActive }) =>
isActive
? theme.background.transparent.light
: theme.background.transparent.lighter};
}
`;
const StyledTabIcon = styled.div`
align-items: center;
display: flex;
justify-content: center;
height: ${({ theme }) => theme.spacing(5)};
width: ${({ theme }) => theme.spacing(5)};
`;
const StyledNewChatButtonWrapper = styled.div<{ isExpanded: boolean }>`
align-items: center;
border: 1px solid ${({ theme }) => theme.border.color.medium};
background: ${({ theme }) => theme.background.secondary};
border-radius: ${({ theme }) => theme.border.radius.pill};
box-sizing: border-box;
display: flex;
height: ${({ theme, isExpanded }) =>
isExpanded ? theme.spacing(7) : theme.spacing(6)};
justify-content: center;
padding: ${({ theme, isExpanded }) =>
isExpanded ? theme.spacing(0.75) : theme.spacing(0.5)};
width: ${({ theme, isExpanded }) =>
isExpanded ? theme.spacing(25.75) : theme.spacing(6)};
transition:
height ${({ theme }) => theme.animation.duration.normal}s ease,
padding ${({ theme }) => theme.animation.duration.normal}s ease;
`;
const StyledNewChatButton = styled.div`
align-items: center;
justify-content: center;
display: flex;
cursor: pointer;
font-size: ${({ theme }) => theme.font.size.sm};
font-weight: ${({ theme }) => theme.font.weight.medium};
gap: ${({ theme }) => theme.spacing(1)};
height: 100%;
width: 100%;
border-radius: inherit;
color: ${({ theme }) => theme.font.color.secondary};
transition:
background ${({ theme }) => theme.animation.duration.fast}s ease,
color ${({ theme }) => theme.animation.duration.fast}s ease;
&:hover {
background: ${({ theme }) => theme.background.transparent.light};
color: ${({ theme }) => theme.font.color.primary};
}
`;
export const MainNavigationDrawerTabsRow = () => {
const theme = useTheme();
const isMobile = useIsMobile();
const isNavigationDrawerExpanded = useAtomStateValue(
isNavigationDrawerExpandedState,
);
const [navigationDrawerActiveTab, setNavigationDrawerActiveTab] =
useAtomState(navigationDrawerActiveTabState);
const { createChatThread } = useCreateNewAIChatThread();
const isAiEnabled = useIsFeatureEnabled(FeatureFlagKey.IS_AI_ENABLED);
const setIsNavigationDrawerExpanded = useSetAtomState(
isNavigationDrawerExpandedState,
);
if (!isAiEnabled) {
return null;
}
const isExpanded = isNavigationDrawerExpanded || isMobile;
const handleTabClick = (tab: NavigationDrawerActiveTab) => () => {
setNavigationDrawerActiveTab(tab);
};
const handleTabKeyDown =
(tab: NavigationDrawerActiveTab) => (event: React.KeyboardEvent) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
setNavigationDrawerActiveTab(tab);
}
};
const handleNewChatClick = () => {
if (isMobile) {
setIsNavigationDrawerExpanded(false);
}
createChatThread();
};
const handleNewChatKeyDown = (event: React.KeyboardEvent) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
handleNewChatClick();
}
};
const getTabIconColor = (isActive: boolean) =>
isActive ? theme.font.color.primary : theme.font.color.tertiary;
return (
<StyledRow isExpanded={isExpanded}>
<NavigationDrawerAnimatedCollapseWrapper>
<StyledTabsPill role="tablist" aria-label={t`Navigation tabs`}>
<StyledTabWrapper
isActive={
navigationDrawerActiveTab ===
NAVIGATION_DRAWER_TABS.NAVIGATION_MENU
}
role="tab"
aria-selected={
navigationDrawerActiveTab ===
NAVIGATION_DRAWER_TABS.NAVIGATION_MENU
}
aria-label={t`Home`}
tabIndex={
navigationDrawerActiveTab ===
NAVIGATION_DRAWER_TABS.NAVIGATION_MENU
? 0
: -1
}
onClick={handleTabClick(NAVIGATION_DRAWER_TABS.NAVIGATION_MENU)}
onKeyDown={handleTabKeyDown(NAVIGATION_DRAWER_TABS.NAVIGATION_MENU)}
>
<StyledTabIcon>
<IconHome
size={theme.icon.size.sm}
color={getTabIconColor(
navigationDrawerActiveTab ===
NAVIGATION_DRAWER_TABS.NAVIGATION_MENU,
)}
/>
</StyledTabIcon>
</StyledTabWrapper>
<StyledTabWrapper
isActive={
navigationDrawerActiveTab ===
NAVIGATION_DRAWER_TABS.AI_CHAT_HISTORY
}
role="tab"
aria-selected={
navigationDrawerActiveTab ===
NAVIGATION_DRAWER_TABS.AI_CHAT_HISTORY
}
aria-label={t`Chat`}
tabIndex={
navigationDrawerActiveTab ===
NAVIGATION_DRAWER_TABS.AI_CHAT_HISTORY
? 0
: -1
}
onClick={handleTabClick(NAVIGATION_DRAWER_TABS.AI_CHAT_HISTORY)}
onKeyDown={handleTabKeyDown(NAVIGATION_DRAWER_TABS.AI_CHAT_HISTORY)}
>
<StyledTabIcon>
<IconComment
size={theme.icon.size.sm}
color={getTabIconColor(
navigationDrawerActiveTab ===
NAVIGATION_DRAWER_TABS.AI_CHAT_HISTORY,
)}
/>
</StyledTabIcon>
</StyledTabWrapper>
</StyledTabsPill>
</NavigationDrawerAnimatedCollapseWrapper>
<StyledNewChatButtonWrapper isExpanded={isExpanded}>
<StyledNewChatButton
role="button"
tabIndex={0}
aria-label={t`New chat`}
onClick={handleNewChatClick}
onKeyDown={handleNewChatKeyDown}
>
<IconMessageCirclePlus size={theme.icon.size.md} />
{isExpanded && t`New chat`}
</StyledNewChatButton>
</StyledNewChatButtonWrapper>
</StyledRow>
);
};
@@ -1,27 +1,29 @@
import { useCreateNewAIChatThread } from '@/ai/hooks/useCreateNewAIChatThread';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useOpenRecordsSearchPageInCommandMenu } from '@/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath';
import { useOpenSettingsMenu } from '@/navigation/hooks/useOpenSettings';
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
import { currentMobileNavigationDrawerState } from '@/navigation/states/currentMobileNavigationDrawerState';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useNavigate } from 'react-router-dom';
import {
type IconComponent,
IconList,
IconMessageCirclePlus,
IconSearch,
IconSettings,
} from 'twenty-ui/display';
import { NavigationBar } from 'twenty-ui/navigation';
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
import { currentMobileNavigationDrawerState } from '@/navigation/states/currentMobileNavigationDrawerState';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
type NavigationBarItemName = 'main' | 'search' | 'tasks' | 'settings';
type NavigationBarItemName = 'main' | 'search' | 'newAIChat';
export const MobileNavigationBar = () => {
const navigate = useNavigate();
@@ -34,7 +36,8 @@ export const MobileNavigationBar = () => {
useAtomState(isNavigationDrawerExpandedState);
const [currentMobileNavigationDrawer, setCurrentMobileNavigationDrawer] =
useAtomState(currentMobileNavigationDrawerState);
const { openSettingsMenu } = useOpenSettingsMenu();
const { createChatThread } = useCreateNewAIChatThread();
const isAiEnabled = useIsFeatureEnabled(FeatureFlagKey.IS_AI_ENABLED);
const { alphaSortedActiveNonSystemObjectMetadataItems } =
useFilteredObjectMetadataItems();
@@ -47,9 +50,7 @@ export const MobileNavigationBar = () => {
? currentMobileNavigationDrawer
: isCommandMenuOpened
? 'search'
: isSettingsPage
? 'settings'
: 'main';
: 'main';
const items: {
name: NavigationBarItemName;
@@ -91,14 +92,19 @@ export const MobileNavigationBar = () => {
openRecordsSearchPage();
},
},
{
name: 'settings',
Icon: IconSettings,
onClick: () => {
closeCommandMenu();
openSettingsMenu();
},
},
...(isAiEnabled
? [
{
name: 'newAIChat' as const,
Icon: IconMessageCirclePlus,
onClick: () => {
setIsNavigationDrawerExpanded(false);
closeCommandMenu();
createChatThread();
},
},
]
: []),
];
return <NavigationBar activeItemName={activeItemName} items={items} />;
@@ -0,0 +1,77 @@
import { useLingui } from '@lingui/react/macro';
import { useLocation, useNavigate } from 'react-router-dom';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import { IconHelpCircle, IconSettings } from 'twenty-ui/display';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { getDocumentationUrl } from '@/support/utils/getDocumentationUrl';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { navigationDrawerExpandedMemorizedState } from '@/ui/navigation/states/navigationDrawerExpandedMemorizedState';
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper';
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 { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection';
import { isNavigationSectionOpenFamilyState } from '@/ui/navigation/navigation-drawer/states/isNavigationSectionOpenFamilyState';
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
export const NavigationDrawerOtherSection = () => {
const { t } = useLingui();
const location = useLocation();
const navigate = useNavigate();
const currentWorkspaceMember = useAtomStateValue(currentWorkspaceMemberState);
const [isNavigationDrawerExpanded, setIsNavigationDrawerExpanded] =
useAtomState(isNavigationDrawerExpandedState);
const setNavigationDrawerExpandedMemorized = useSetAtomState(
navigationDrawerExpandedMemorizedState,
);
const setNavigationMemorizedUrl = useSetAtomState(
navigationMemorizedUrlState,
);
const { toggleNavigationSection } = useNavigationSection('Other');
const isNavigationSectionOpen = useAtomFamilyStateValue(
isNavigationSectionOpenFamilyState,
'Other',
);
const handleSettingsClick = () => {
setNavigationDrawerExpandedMemorized(isNavigationDrawerExpanded);
setIsNavigationDrawerExpanded(true);
setNavigationMemorizedUrl(location.pathname + location.search);
navigate(getSettingsPath(SettingsPath.ProfilePage));
};
return (
<NavigationDrawerSection>
<NavigationDrawerAnimatedCollapseWrapper>
<NavigationDrawerSectionTitle
label={t`Other`}
onClick={toggleNavigationSection}
/>
</NavigationDrawerAnimatedCollapseWrapper>
{isNavigationSectionOpen && (
<>
<NavigationDrawerItem
label={t`Settings`}
Icon={IconSettings}
onClick={handleSettingsClick}
/>
<NavigationDrawerItem
label={t`Documentation`}
to={getDocumentationUrl({
locale: currentWorkspaceMember?.locale,
})}
Icon={IconHelpCircle}
/>
</>
)}
</NavigationDrawerSection>
);
};