feat: improve mobile display by tab bar and other changes (#2304)

* feat: improve mobile display by tab bar and other changes

* fix: remove unused declaration in mobile navigation

* fix: update desktop navbar stories title

* fix: retrieve old titles for desktop-navbar stories

* fix: styles, manage active tabs

* fix: styles, manage active tabs

* fix: styles, manage active tabs

* fix: styles, manage active tabs

* fix: styles, manage active tabs

* fix: styles, manage active tabs

* fix: styles, manage active tabs

* fix: styles, manage active tabs

* fix: update logic for tab bar menu icons

* fix: remove Settings icon for mobile

* fix: resolve comments in pl

* feat: rework mobile navigation bar

* Fix

* Fixes

---------

Co-authored-by: Thaïs Guigon <guigon.thais@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Saba Shavidze
2023-12-02 02:16:34 +04:00
committed by GitHub
parent 74b077f3ca
commit fec8223ab8
50 changed files with 640 additions and 380 deletions

View File

@ -2,8 +2,8 @@ import styled from '@emotion/styled';
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
import { DraggableList } from '@/ui/layout/draggable-list/components/DraggableList';
import NavItem from '@/ui/navigation/navbar/components/NavItem';
import NavTitle from '@/ui/navigation/navbar/components/NavTitle';
import NavItem from '@/ui/navigation/navigation-drawer/components/NavItem';
import NavTitle from '@/ui/navigation/navigation-drawer/components/NavTitle';
import { Avatar } from '@/users/components/Avatar';
import { useFavorites } from '../hooks/useFavorites';

View File

@ -0,0 +1,64 @@
import { useLocation, useNavigate } from 'react-router-dom';
import { useSetRecoilState } from 'recoil';
import { useCurrentUserTaskCount } from '@/activities/tasks/hooks/useCurrentUserDueTaskCount';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { Favorites } from '@/favorites/components/Favorites';
import { useIsTasksPage } from '@/navigation/hooks/useIsTasksPage';
import { SettingsNavbar } from '@/settings/components/SettingsNavbar';
import {
IconBell,
IconCheckbox,
IconSearch,
IconSettings,
} from '@/ui/display/icon/index';
import MainNavbar from '@/ui/navigation/navigation-drawer/components/MainNavbar';
import NavItem from '@/ui/navigation/navigation-drawer/components/NavItem';
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
import { useIsSettingsPage } from '../hooks/useIsSettingsPage';
import { WorkspaceNavItems } from './WorkspaceNavItems';
export const DesktopNavigationDrawer = () => {
const { toggleCommandMenu } = useCommandMenu();
const isSettingsPage = useIsSettingsPage();
const isTasksPage = useIsTasksPage();
const { currentUserDueTaskCount } = useCurrentUserTaskCount();
const navigate = useNavigate();
const location = useLocation();
const setNavigationMemorizedUrl = useSetRecoilState(
navigationMemorizedUrlState,
);
return isSettingsPage ? (
<SettingsNavbar />
) : (
<MainNavbar>
<NavItem
label="Search"
Icon={IconSearch}
onClick={toggleCommandMenu}
keyboard={['⌘', 'K']}
/>
<NavItem label="Notifications" to="/inbox" Icon={IconBell} soon />
<NavItem
label="Settings"
onClick={() => {
setNavigationMemorizedUrl(location.pathname + location.search);
navigate('/settings/profile');
}}
Icon={IconSettings}
/>
<NavItem
label="Tasks"
to="/tasks"
active={isTasksPage}
Icon={IconCheckbox}
count={currentUserDueTaskCount}
/>
<Favorites />
<WorkspaceNavItems />
</MainNavbar>
);
};

View File

@ -0,0 +1,85 @@
import { useNavigate } from 'react-router-dom';
import { useRecoilState } from 'recoil';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import {
IconCheckbox,
IconList,
IconSearch,
IconSettings,
} from '@/ui/display/icon';
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
import { NavigationBar } from '@/ui/navigation/navigation-bar/components/NavigationBar';
import { navigationDrawerState } from '@/ui/navigation/states/navigationDrawerState';
import { useIsSettingsPage } from '../hooks/useIsSettingsPage';
import { useIsTasksPage } from '../hooks/useIsTasksPage';
type NavigationBarItemName = 'main' | 'search' | 'tasks' | 'settings';
export const MobileNavigationBar = () => {
const [isCommandMenuOpened] = useRecoilState(isCommandMenuOpenedState);
const { closeCommandMenu, toggleCommandMenu } = useCommandMenu();
const isTasksPage = useIsTasksPage();
const isSettingsPage = useIsSettingsPage();
const navigate = useNavigate();
const [navigationDrawer, setNavigationDrawer] = useRecoilState(
navigationDrawerState,
);
const initialActiveItemName: NavigationBarItemName = isCommandMenuOpened
? 'search'
: isTasksPage
? 'tasks'
: isSettingsPage
? 'settings'
: 'main';
const items: {
name: NavigationBarItemName;
Icon: IconComponent;
onClick: () => void;
}[] = [
{
name: 'main',
Icon: IconList,
onClick: () => {
closeCommandMenu();
setNavigationDrawer(navigationDrawer === 'main' ? '' : 'main');
},
},
{
name: 'search',
Icon: IconSearch,
onClick: () => {
setNavigationDrawer('');
toggleCommandMenu();
},
},
{
name: 'tasks',
Icon: IconCheckbox,
onClick: () => {
closeCommandMenu();
setNavigationDrawer('');
navigate('/tasks');
},
},
{
name: 'settings',
Icon: IconSettings,
onClick: () => {
closeCommandMenu();
setNavigationDrawer(navigationDrawer === 'settings' ? '' : 'settings');
},
},
];
return (
<NavigationBar
activeItemName={navigationDrawer || initialActiveItemName}
items={items}
/>
);
};

View File

@ -0,0 +1,21 @@
import { useRecoilState } from 'recoil';
import { Favorites } from '@/favorites/components/Favorites';
import { SettingsNavbar } from '@/settings/components/SettingsNavbar';
import MainNavbar from '@/ui/navigation/navigation-drawer/components/MainNavbar';
import { navigationDrawerState } from '@/ui/navigation/states/navigationDrawerState';
import { WorkspaceNavItems } from './WorkspaceNavItems';
export const MobileNavigationDrawer = () => {
const [navigationDrawer] = useRecoilState(navigationDrawerState);
return navigationDrawer === 'settings' ? (
<SettingsNavbar />
) : (
<MainNavbar>
<Favorites />
<WorkspaceNavItems />
</MainNavbar>
);
};

View File

@ -0,0 +1,23 @@
import { useLocation } from 'react-router-dom';
import { ObjectMetadataNavItems } from '@/object-metadata/components/ObjectMetadataNavItems';
import { IconTargetArrow } from '@/ui/display/icon/index';
import NavItem from '@/ui/navigation/navigation-drawer/components/NavItem';
import NavTitle from '@/ui/navigation/navigation-drawer/components/NavTitle';
export const WorkspaceNavItems = () => {
const { pathname } = useLocation();
return (
<>
<NavTitle label="Workspace" />
<ObjectMetadataNavItems />
<NavItem
label="Opportunities"
to="/objects/opportunities"
active={pathname === '/objects/opportunities'}
Icon={IconTargetArrow}
/>
</>
);
};

View File

@ -0,0 +1,23 @@
import { Meta, StoryObj } from '@storybook/react';
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
import { DesktopNavigationDrawer } from '../DesktopNavigationDrawer';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
const meta: Meta<typeof DesktopNavigationDrawer> = {
title: 'Modules/Navigation/DesktopNavigationDrawer',
component: DesktopNavigationDrawer,
};
export default meta;
type Story = StoryObj<typeof DesktopNavigationDrawer>;
export const Default: Story = {
decorators: [
ComponentDecorator,
ComponentWithRouterDecorator,
SnackBarDecorator,
],
};

View File

@ -0,0 +1,23 @@
import { Meta, StoryObj } from '@storybook/react';
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
import { MobileNavigationDrawer } from '../MobileNavigationDrawer';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
const meta: Meta<typeof MobileNavigationDrawer> = {
title: 'Modules/Navigation/MobileNavigationDrawer',
component: MobileNavigationDrawer,
};
export default meta;
type Story = StoryObj<typeof MobileNavigationDrawer>;
export const Default: Story = {
decorators: [
ComponentDecorator,
ComponentWithRouterDecorator,
SnackBarDecorator,
],
};

View File

@ -0,0 +1,4 @@
import { useLocation } from 'react-router-dom';
export const useIsSettingsPage = () =>
useLocation().pathname.match(/\/settings\//g) !== null;

View File

@ -0,0 +1,3 @@
import { useLocation } from 'react-router-dom';
export const useIsTasksPage = () => useLocation().pathname === '/tasks';

View File

@ -3,7 +3,7 @@ import { useLocation, useNavigate } from 'react-router-dom';
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
import { Icon123 } from '@/ui/input/constants/icons';
import { useLazyLoadIcons } from '@/ui/input/hooks/useLazyLoadIcons';
import NavItem from '@/ui/navigation/navbar/components/NavItem';
import NavItem from '@/ui/navigation/navigation-drawer/components/NavItem';
export const ObjectMetadataNavItems = () => {
const { activeObjectMetadataItems } = useObjectMetadataItemForSettings();

View File

@ -89,12 +89,14 @@ export const RecordTableContainer = ({
}}
/>
<RecordTableEffect recordTableId={recordTableId} viewBarId={viewBarId} />
<RecordTable
recordTableId={recordTableId}
viewBarId={viewBarId}
updateRecordMutation={updateEntity}
createRecord={createRecord}
/>
{
<RecordTable
recordTableId={recordTableId}
viewBarId={viewBarId}
updateRecordMutation={updateEntity}
createRecord={createRecord}
/>
}
</StyledContainer>
);
};

View File

@ -12,9 +12,9 @@ import {
IconUserCircle,
IconUsers,
} from '@/ui/display/icon/index';
import NavItem from '@/ui/navigation/navbar/components/NavItem';
import NavTitle from '@/ui/navigation/navbar/components/NavTitle';
import SubMenuNavbar from '@/ui/navigation/navbar/components/SubMenuNavbar';
import NavItem from '@/ui/navigation/navigation-drawer/components/NavItem';
import NavTitle from '@/ui/navigation/navigation-drawer/components/NavTitle';
import SubMenuNavbar from '@/ui/navigation/navigation-drawer/components/SubMenuNavbar';
export const SettingsNavbar = () => {
const navigate = useNavigate();

View File

@ -1,4 +1,4 @@
import { IconArchiveOff, IconDotsVertical, IconTrash } from '@/ui/display/icon';
import { IconArchiveOff, IconDotsVertical } from '@/ui/display/icon';
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
@ -15,9 +15,7 @@ type SettingsObjectFieldDisabledActionDropdownProps = {
};
export const SettingsObjectFieldDisabledActionDropdown = ({
isCustomField,
onActivate,
onErase,
scopeKey,
}: SettingsObjectFieldDisabledActionDropdownProps) => {
const dropdownScopeId = `${scopeKey}-settings-field-disabled-action-dropdown`;
@ -29,10 +27,10 @@ export const SettingsObjectFieldDisabledActionDropdown = ({
closeDropdown();
};
const handleErase = () => {
onErase();
closeDropdown();
};
// const handleErase = () => {
// onErase();
// closeDropdown();
// };
return (
<DropdownScope dropdownScopeId={dropdownScopeId}>
@ -48,14 +46,14 @@ export const SettingsObjectFieldDisabledActionDropdown = ({
LeftIcon={IconArchiveOff}
onClick={handleActivate}
/>
{isCustomField && (
{/* {isCustomField && (
<MenuItem
text="Erase"
accent="danger"
LeftIcon={IconTrash}
onClick={handleErase}
/>
)}
)} */}
</DropdownMenuItemsContainer>
</DropdownMenu>
}

View File

@ -1,4 +1,4 @@
import { IconDotsVertical, IconTrash } from '@/ui/display/icon';
import { IconDotsVertical } from '@/ui/display/icon';
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
import { IconArchiveOff } from '@/ui/input/constants/icons';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
@ -16,9 +16,7 @@ type SettingsObjectDisabledMenuDropDownProps = {
};
export const SettingsObjectDisabledMenuDropDown = ({
isCustomObject,
onActivate,
onErase,
scopeKey,
}: SettingsObjectDisabledMenuDropDownProps) => {
const dropdownScopeId = `${scopeKey}-settings-object-disabled-menu-dropdown`;
@ -30,10 +28,10 @@ export const SettingsObjectDisabledMenuDropDown = ({
closeDropdown();
};
const handleErase = () => {
onErase();
closeDropdown();
};
// const handleErase = () => {
// onErase();
// closeDropdown();
// };
return (
<DropdownScope dropdownScopeId={dropdownScopeId}>
@ -49,14 +47,14 @@ export const SettingsObjectDisabledMenuDropDown = ({
LeftIcon={IconArchiveOff}
onClick={handleActivate}
/>
{isCustomObject && (
{/* {isCustomObject && (
<MenuItem
text="Erase"
LeftIcon={IconTrash}
accent="danger"
onClick={handleErase}
/>
)}
)} */}
</DropdownMenuItemsContainer>
</DropdownMenu>
}

View File

@ -0,0 +1,6 @@
import { useLocation } from 'react-router-dom';
export const useIsMenuNavbarDisplayed = () => {
const currentPath = useLocation().pathname;
return currentPath.match(/^\/companies(\/.*)?$/) !== null;
};

View File

@ -1,6 +0,0 @@
import { useLocation } from 'react-router-dom';
export const useIsSubMenuNavbarDisplayed = () => {
const currentPath = useLocation().pathname;
return currentPath.match(/\/settings\//g) !== null;
};

View File

@ -1,6 +1,6 @@
import { ReactNode } from 'react';
import styled from '@emotion/styled';
import { AnimatePresence, LayoutGroup } from 'framer-motion';
import { useRecoilValue } from 'recoil';
import { AuthModal } from '@/auth/components/Modal';
import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus';
@ -8,23 +8,22 @@ import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus';
import { CommandMenu } from '@/command-menu/components/CommandMenu';
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
import { KeyboardShortcutMenu } from '@/keyboard-shortcut-menu/components/KeyboardShortcutMenu';
import { DesktopNavigationDrawer } from '@/navigation/components/DesktopNavigationDrawer';
import { MobileNavigationBar } from '@/navigation/components/MobileNavigationBar';
import { MobileNavigationDrawer } from '@/navigation/components/MobileNavigationDrawer';
import { SignInBackgroundMockPage } from '@/sign-in-background-mock/components/SignInBackgroundMockPage';
import { NavbarAnimatedContainer } from '@/ui/navigation/navbar/components/NavbarAnimatedContainer';
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme';
import { AppNavbar } from '~/AppNavbar';
import { isNavbarOpenedState } from '../states/isNavbarOpenedState';
import { NavbarAnimatedContainer } from '@/ui/navigation/navigation-drawer/components/NavbarAnimatedContainer';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
const StyledLayout = styled.div`
background: ${({ theme }) => theme.background.noisy};
display: flex;
flex-direction: row;
flex-direction: column;
height: 100vh;
position: relative;
scrollbar-color: ${({ theme }) => theme.border.color.medium};
scrollbar-width: 4px;
width: 100vw;
width: 100%;
*::-webkit-scrollbar {
height: 4px;
@ -41,43 +40,51 @@ const StyledLayout = styled.div`
}
`;
const StyledMainContainer = styled.div`
const StyledPageContainer = styled.div`
display: flex;
flex: 1;
flex-direction: row;
`;
const StyledMainContainer = styled.div`
display: flex;
flex: 0 1 100%;
flex-direction: row;
overflow: hidden;
@media (max-width: ${MOBILE_VIEWPORT}px) {
width: ${() => (useRecoilValue(isNavbarOpenedState) ? '0' : '100%')};
}
`;
type DefaultLayoutProps = {
children: React.ReactNode;
children: ReactNode;
};
export const DefaultLayout = ({ children }: DefaultLayoutProps) => {
const onboardingStatus = useOnboardingStatus();
const isMobile = useIsMobile();
return (
<StyledLayout>
<CommandMenu />
<KeyboardShortcutMenu />
<NavbarAnimatedContainer>
<AppNavbar />
</NavbarAnimatedContainer>
<StyledMainContainer>
{onboardingStatus && onboardingStatus !== OnboardingStatus.Completed ? (
<>
<SignInBackgroundMockPage />
<AnimatePresence mode="wait">
<LayoutGroup>
<AuthModal>{children}</AuthModal>
</LayoutGroup>
</AnimatePresence>
</>
) : (
<AppErrorBoundary>{children}</AppErrorBoundary>
)}
</StyledMainContainer>
<StyledPageContainer>
<NavbarAnimatedContainer>
{isMobile ? <MobileNavigationDrawer /> : <DesktopNavigationDrawer />}
</NavbarAnimatedContainer>
<StyledMainContainer>
{onboardingStatus &&
onboardingStatus !== OnboardingStatus.Completed ? (
<>
<SignInBackgroundMockPage />
<AnimatePresence mode="wait">
<LayoutGroup>
<AuthModal>{children}</AuthModal>
</LayoutGroup>
</AnimatePresence>
</>
) : (
<AppErrorBoundary>{children}</AppErrorBoundary>
)}
</StyledMainContainer>
</StyledPageContainer>
{isMobile && <MobileNavigationBar />}
</StyledLayout>
);
};

View File

@ -1,12 +1 @@
import { PAGE_BAR_MIN_HEIGHT } from './PageHeader';
import { RightDrawerContainer } from './RightDrawerContainer';
type PageBodyProps = {
children: JSX.Element | JSX.Element[];
};
export const PageBody = ({ children }: PageBodyProps) => (
<RightDrawerContainer topMargin={PAGE_BAR_MIN_HEIGHT + 16 + 16}>
{children}
</RightDrawerContainer>
);
export { RightDrawerContainer as PageBody } from './RightDrawerContainer';

View File

@ -1,15 +1,9 @@
import styled from '@emotion/styled';
type PageContainerProps = {
children: JSX.Element | JSX.Element[];
};
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
width: 100%;
`;
export const PageContainer = ({ children }: PageContainerProps) => (
<StyledContainer>{children}</StyledContainer>
);
export { StyledContainer as PageContainer };

View File

@ -1,4 +1,4 @@
import { ComponentProps, useCallback } from 'react';
import { ComponentProps, ReactNode } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
@ -7,15 +7,12 @@ import { useRecoilValue } from 'recoil';
import { IconChevronLeft } from '@/ui/display/icon/index';
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
import { OverflowingTextWithTooltip } from '@/ui/display/tooltip/OverflowingTextWithTooltip';
import {
IconButton,
IconButtonSize,
} from '@/ui/input/button/components/IconButton';
import NavCollapseButton from '@/ui/navigation/navbar/components/NavCollapseButton';
import { IconButton } from '@/ui/input/button/components/IconButton';
import NavCollapseButton from '@/ui/navigation/navigation-drawer/components/NavCollapseButton';
import { navigationDrawerState } from '@/ui/navigation/states/navigationDrawerState';
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { isNavbarOpenedState } from '../states/isNavbarOpenedState';
export const PAGE_BAR_MIN_HEIGHT = 40;
const StyledTopBarContainer = styled.div`
@ -31,13 +28,23 @@ const StyledTopBarContainer = styled.div`
padding-left: 0;
padding-right: ${({ theme }) => theme.spacing(3)};
z-index: 20;
@media (max-width: ${MOBILE_VIEWPORT}px) {
padding-left: ${({ theme }) => theme.spacing(3)};
}
`;
const StyledLeftContainer = styled.div`
align-items: center;
display: flex;
flex-direction: row;
gap: ${({ theme }) => theme.spacing(1)};
padding-left: ${({ theme }) => theme.spacing(2)};
width: 100%;
@media (max-width: ${MOBILE_VIEWPORT}px) {
padding-left: ${({ theme }) => theme.spacing(1)};
}
`;
const StyledTitleContainer = styled.div`
@ -47,24 +54,15 @@ const StyledTitleContainer = styled.div`
max-width: 50%;
`;
const StyledTopBarButtonContainer = styled.div`
margin-left: ${({ theme }) => theme.spacing(1)};
margin-right: ${({ theme }) => theme.spacing(1)};
`;
const StyledBackIconButton = styled(IconButton)`
margin-right: ${({ theme }) => theme.spacing(1)};
`;
const StyledTopBarIconStyledTitleContainer = styled.div<{
hideLeftPadding?: boolean;
}>`
const StyledTopBarIconStyledTitleContainer = styled.div`
align-items: center;
display: flex;
flex: 1 0 100%;
flex-direction: row;
padding-left: ${({ theme, hideLeftPadding }) =>
hideLeftPadding ? theme.spacing(2) : undefined};
width: 100%;
`;
const StyledPageActionContainer = styled.div`
@ -72,11 +70,16 @@ const StyledPageActionContainer = styled.div`
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledTopBarButtonContainer = styled.div`
margin-left: ${({ theme }) => theme.spacing(1)};
margin-right: ${({ theme }) => theme.spacing(1)};
`;
type PageHeaderProps = ComponentProps<'div'> & {
title: string;
hasBackButton?: boolean;
Icon: IconComponent;
children?: JSX.Element | JSX.Element[];
children?: ReactNode;
};
export const PageHeader = ({
@ -85,33 +88,28 @@ export const PageHeader = ({
Icon,
children,
}: PageHeaderProps) => {
const isMobile = useIsMobile();
const navigate = useNavigate();
const navigateBack = useCallback(() => navigate(-1), [navigate]);
const isNavbarOpened = useRecoilValue(isNavbarOpenedState);
const iconSize: IconButtonSize = useIsMobile() ? 'small' : 'medium';
const theme = useTheme();
const navigationDrawer = useRecoilValue(navigationDrawerState);
return (
<StyledTopBarContainer>
<StyledLeftContainer>
{!isNavbarOpened && (
{navigationDrawer === '' && (
<StyledTopBarButtonContainer>
<NavCollapseButton direction="right" />
</StyledTopBarButtonContainer>
)}
{hasBackButton && (
<StyledTopBarButtonContainer>
<StyledBackIconButton
Icon={IconChevronLeft}
size={iconSize}
onClick={navigateBack}
variant="tertiary"
/>
</StyledTopBarButtonContainer>
<StyledBackIconButton
Icon={IconChevronLeft}
size={isMobile ? 'small' : 'medium'}
onClick={() => navigate(-1)}
variant="tertiary"
/>
)}
<StyledTopBarIconStyledTitleContainer hideLeftPadding={!hasBackButton}>
<StyledTopBarIconStyledTitleContainer>
{Icon && <Icon size={theme.icon.size.md} />}
<StyledTitleContainer data-testid="top-bar-title">
<OverflowingTextWithTooltip text={title} />

View File

@ -1,25 +1,30 @@
import { ReactNode } from 'react';
import styled from '@emotion/styled';
import { RightDrawer } from '@/ui/layout/right-drawer/components/RightDrawer';
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme';
import { PagePanel } from './PagePanel';
type RightDrawerContainerProps = {
children: JSX.Element | JSX.Element[];
topMargin?: number;
children: ReactNode;
};
const StyledMainContainer = styled.div<{ topMargin: number }>`
const StyledMainContainer = styled.div`
background: ${({ theme }) => theme.background.noisy};
box-sizing: border-box;
display: flex;
flex-direction: row;
gap: ${({ theme }) => theme.spacing(2)};
height: calc(100% - ${(props) => props.topMargin}px);
height: 100%;
padding-bottom: ${({ theme }) => theme.spacing(3)};
padding-right: ${({ theme }) => theme.spacing(3)};
width: calc(100% - ${({ theme }) => theme.spacing(3)});
width: 100%;
@media (max-width: ${MOBILE_VIEWPORT}px) {
padding-left: ${({ theme }) => theme.spacing(3)};
padding-bottom: 0;
}
`;
type LeftContainerProps = {
@ -35,9 +40,8 @@ const StyledLeftContainer = styled.div<LeftContainerProps>`
export const RightDrawerContainer = ({
children,
topMargin,
}: RightDrawerContainerProps) => (
<StyledMainContainer topMargin={topMargin ?? 0}>
<StyledMainContainer>
<StyledLeftContainer>
<PagePanel>{children}</PagePanel>
</StyledLeftContainer>

View File

@ -30,7 +30,7 @@ export const SubMenuTopBarContainer = ({
return (
<StyledContainer isMobile={isMobile}>
{isMobile && <PageHeader title={title} Icon={Icon} />}
<RightDrawerContainer topMargin={16}>{children}</RightDrawerContainer>
<RightDrawerContainer>{children}</RightDrawerContainer>
</StyledContainer>
);
};

View File

@ -13,7 +13,6 @@ import {
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { isDefined } from '~/utils/isDefined';
import { leftNavbarWidth } from '../../../navigation/navbar/constants';
import { useRightDrawer } from '../hooks/useRightDrawer';
import { isRightDrawerExpandedState } from '../states/isRightDrawerExpandedState';
import { isRightDrawerOpenState } from '../states/isRightDrawerOpenState';
@ -70,15 +69,9 @@ export const RightDrawer = () => {
const isMobile = useIsMobile();
const rightDrawerWidthExpanded = `calc(100% - ${
leftNavbarWidth.desktop
} - ${theme.spacing(2)})`;
const rightDrawerWidth = isRightDrawerOpen
? isMobile
? isMobile || isRightDrawerExpanded
? '100%'
: isRightDrawerExpanded
? rightDrawerWidthExpanded
: theme.rightDrawerWidth
: '0';

View File

@ -1,10 +0,0 @@
import { atom } from 'recoil';
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme';
const isMobile = window.innerWidth <= MOBILE_VIEWPORT;
export const isNavbarOpenedState = atom({
key: 'ui/isNavbarOpenedState',
default: !isMobile,
});

View File

@ -1,6 +0,0 @@
import { atom } from 'recoil';
export const isNavbarSwitchingSizeState = atom({
key: 'ui/isNavbarSwitchingSizeState',
default: true,
});

View File

@ -1,63 +0,0 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { motion } from 'framer-motion';
import { useRecoilState, useRecoilValue } from 'recoil';
import { useIsSubMenuNavbarDisplayed } from '@/ui/layout/hooks/useIsSubMenuNavbarDisplayed';
import { isNavbarOpenedState } from '@/ui/layout/states/isNavbarOpenedState';
import { isNavbarSwitchingSizeState } from '@/ui/layout/states/isNavbarSwitchingSizeState';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { leftNavbarWidth, leftSubMenuNavbarWidth } from '../constants';
const StyledNavbarContainer = styled(motion.div)`
align-items: end;
display: flex;
flex-direction: column;
flex-shrink: 0;
overflow: hidden;
padding: ${({ theme }) => theme.spacing(2)};
`;
type NavbarAnimatedContainerProps = {
children: React.ReactNode;
};
export const NavbarAnimatedContainer = ({
children,
}: NavbarAnimatedContainerProps) => {
const isNavbarOpened = useRecoilValue(isNavbarOpenedState);
const [, setIsNavbarSwitchingSize] = useRecoilState(
isNavbarSwitchingSizeState,
);
const isInSubMenu = useIsSubMenuNavbarDisplayed();
const theme = useTheme();
const isMobile = useIsMobile();
const leftBarWidth = isInSubMenu
? isMobile
? leftSubMenuNavbarWidth.mobile
: leftSubMenuNavbarWidth.desktop
: isMobile
? leftNavbarWidth.mobile
: leftNavbarWidth.desktop;
return (
<StyledNavbarContainer
onAnimationComplete={() => {
setIsNavbarSwitchingSize(false);
}}
initial={false}
animate={{
width: isNavbarOpened ? leftBarWidth : '0',
opacity: isNavbarOpened ? 1 : 0,
}}
transition={{
duration: theme.animation.duration.normal,
}}
>
{children}
</StyledNavbarContainer>
);
};

View File

@ -1,11 +0,0 @@
export const leftNavbarWidth = {
mobile: 'calc(100% - 16px)',
desktop: '220px',
};
export const leftSubMenuNavbarWidth = {
mobile: 'calc(100% - 16px)',
desktop: '520px',
};
export const githubLink = 'https://github.com/twentyhq/twenty';

View File

@ -0,0 +1,33 @@
import styled from '@emotion/styled';
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
import { NavigationBarItem } from './NavigationBarItem';
const StyledContainer = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(4)};
justify-content: center;
padding: ${({ theme }) => theme.spacing(3)};
`;
type NavigationBarProps = {
activeItemName: string;
items: { name: string; Icon: IconComponent; onClick: () => void }[];
};
export const NavigationBar = ({
activeItemName,
items,
}: NavigationBarProps) => (
<StyledContainer>
{items.map(({ Icon, name, onClick }) => (
<NavigationBarItem
key={name}
Icon={Icon}
isActive={activeItemName === name}
onClick={onClick}
/>
))}
</StyledContainer>
);

View File

@ -0,0 +1,42 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
const StyledIconButton = styled.div<{ isActive?: boolean }>`
align-items: center;
background-color: ${({ isActive, theme }) =>
isActive ? theme.background.transparent.light : 'none'};
border-radius: ${({ theme }) => theme.spacing(1)};
cursor: pointer;
display: flex;
height: ${({ theme }) => theme.spacing(10)};
justify-content: center;
transition: background-color ${({ theme }) => theme.animation.duration.fast}s
ease;
width: ${({ theme }) => theme.spacing(10)};
&:hover {
background-color: ${({ theme }) => theme.background.transparent.light};
}
`;
type NavigationBarItemProps = {
Icon: IconComponent;
isActive: boolean;
onClick: () => void;
};
export const NavigationBarItem = ({
Icon,
isActive,
onClick,
}: NavigationBarItemProps) => {
const theme = useTheme();
return (
<StyledIconButton isActive={isActive} onClick={onClick}>
<Icon color={theme.color.gray50} size={theme.icon.size.lg} />
</StyledIconButton>
);
};

View File

@ -0,0 +1,33 @@
import { Meta, StoryObj } from '@storybook/react';
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
import { NavigationBar } from '../NavigationBar';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import {
IconList,
IconSearch,
IconCheckbox,
IconSettings,
} from '@/ui/display/icon';
const meta: Meta<typeof NavigationBar> = {
title: 'UI/Navigation/NavigationBar/NavigationBar',
component: NavigationBar,
args: {
activeItemName: 'main',
items: [
{ name: 'main', Icon: IconList, onClick: () => undefined },
{ name: 'search', Icon: IconSearch, onClick: () => undefined },
{ name: 'tasks', Icon: IconCheckbox, onClick: () => undefined },
{ name: 'settings', Icon: IconSettings, onClick: () => undefined },
],
},
};
export default meta;
type Story = StoryObj<typeof NavigationBar>;
export const Default: Story = {
decorators: [ComponentDecorator, ComponentWithRouterDecorator],
};

View File

@ -10,11 +10,13 @@ type MainNavbarProps = {
};
const StyledContainer = styled.div`
box-sizing: border-box;
display: flex;
flex-direction: column;
height: 100%;
justify-content: space-between;
margin-bottom: ${({ theme }) => theme.spacing(2.5)};
padding: ${({ theme }) => theme.spacing(2)};
width: 100%;
`;

View File

@ -1,9 +1,9 @@
import { useNavigate } from 'react-router-dom';
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { useRecoilValue } from 'recoil';
import { IconChevronLeft } from '@/ui/display/icon/index';
import { isNavbarSwitchingSizeState } from '@/ui/layout/states/isNavbarSwitchingSizeState';
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
type NavBackButtonProps = {
title: string;
@ -32,24 +32,19 @@ const StyledContainer = styled.div`
const NavBackButton = ({ title }: NavBackButtonProps) => {
const navigate = useNavigate();
const [, setIsNavbarSwitchingSize] = useRecoilState(
isNavbarSwitchingSizeState,
);
const navigationMemorizedUrl = useRecoilValue(navigationMemorizedUrlState);
return (
<>
<StyledContainer>
<StyledIconAndButtonContainer
onClick={() => {
setIsNavbarSwitchingSize(true);
navigate('/', { replace: true });
}}
>
<IconChevronLeft />
<span>{title}</span>
</StyledIconAndButtonContainer>
</StyledContainer>
</>
<StyledContainer>
<StyledIconAndButtonContainer
onClick={() => {
navigate(navigationMemorizedUrl, { replace: true });
}}
>
<IconChevronLeft />
<span>{title}</span>
</StyledIconAndButtonContainer>
</StyledContainer>
);
};

View File

@ -1,14 +1,14 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { motion } from 'framer-motion';
import { useRecoilState } from 'recoil';
import { useSetRecoilState } from 'recoil';
import {
IconLayoutSidebarLeftCollapse,
IconLayoutSidebarRightCollapse,
} from '@/ui/display/icon';
import { IconButton } from '@/ui/input/button/components/IconButton';
import { isNavbarOpenedState } from '@/ui/layout/states/isNavbarOpenedState';
import { navigationDrawerState } from '@/ui/navigation/states/navigationDrawerState';
const StyledCollapseButton = styled(motion.div)`
align-items: center;
@ -41,8 +41,7 @@ const NavCollapseButton = ({
direction = 'left',
show = true,
}: NavCollapseButtonProps) => {
const [isNavbarOpened, setIsNavbarOpened] =
useRecoilState(isNavbarOpenedState);
const setNavigationDrawer = useSetRecoilState(navigationDrawerState);
const iconSize = 'small';
const theme = useTheme();
@ -57,7 +56,11 @@ const NavCollapseButton = ({
transition={{
duration: theme.animation.duration.normal,
}}
onClick={() => setIsNavbarOpened(!isNavbarOpened)}
onClick={() =>
setNavigationDrawer((navigationDrawer) =>
navigationDrawer === '' ? 'main' : '',
)
}
>
<IconButton
Icon={

View File

@ -1,15 +1,15 @@
import { useNavigate } from 'react-router-dom';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { useSetRecoilState } from 'recoil';
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
import { navigationDrawerState } from '@/ui/navigation/states/navigationDrawerState';
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { isNavbarOpenedState } from '../../../layout/states/isNavbarOpenedState';
type NavItemProps = {
className?: string;
label: string;
to?: string;
onClick?: () => void;
@ -115,6 +115,7 @@ const StyledKeyBoardShortcut = styled.div`
`;
const NavItem = ({
className,
label,
Icon,
to,
@ -126,25 +127,26 @@ const NavItem = ({
keyboard,
}: NavItemProps) => {
const theme = useTheme();
const navigate = useNavigate();
const [, setIsNavbarOpened] = useRecoilState(isNavbarOpenedState);
const isMobile = useIsMobile();
const navigate = useNavigate();
const setNavigationDrawer = useSetRecoilState(navigationDrawerState);
const handleItemClick = () => {
if (isMobile) {
setIsNavbarOpened(false);
setNavigationDrawer('');
}
if (onClick) {
onClick();
} else if (to) {
navigate(to);
return;
}
if (to) navigate(to);
};
return (
<StyledItem
className={className}
onClick={handleItemClick}
active={active}
aria-selected={active}
@ -157,7 +159,7 @@ const NavItem = ({
{!!count && <StyledItemCount>{count}</StyledItemCount>}
{keyboard && (
<StyledKeyBoardShortcut className="keyboard-shortcuts">
{keyboard.map((key) => key)}
{keyboard}
</StyledKeyBoardShortcut>
)}
</StyledItem>

View File

@ -2,6 +2,7 @@ import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { getImageAbsoluteURIOrBase64 } from '@/users/utils/getProfilePictureAbsoluteURI';
import NavCollapseButton from './NavCollapseButton';
@ -53,7 +54,7 @@ const NavWorkspaceButton = ({
showCollapseButton,
}: NavWorkspaceButtonProps) => {
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const isMobile = useIsMobile();
const DEFAULT_LOGO =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA7EAAAOxAGVKw4bAAACb0lEQVR4nO2VO4taQRTHr3AblbjxEVlwCwVhg7BoqqCIjy/gAyyFWNlYBOxsfH0KuxgQGwXRUkGuL2S7i1barGAgiwbdW93SnGOc4BonPiKahf3DwXFmuP/fPM4ZlvmlTxAhCBdzHnEQWYiv7Mr4C3NeuVYhQYDPzOUUQgDLBQGcLHNhvQK8DACPx8PTxiqVyvISG43GbyaT6Qfpn06n0m63e/tPAPF4vJ1MJu8kEsnWTCkWi1yr1RKGw+GDRqPBOTfr44vFQvD7/Q/lcpmaaVQAr9fLp1IpO22c47hGOBz+MB6PH+Vy+VYDAL8qlUoGtVotzOfzq4MAgsHgE/6KojiQyWR/bKVSqbSszHFM8Pl8z1YK48JsNltCOBwOnrYLO+8AAIjb+nHbycoTiUQfDJ7tFq4YAHiVSmXBxcD41u8flQU8z7fhzO0r83atVns3Go3u9Xr9x0O/RQXo9/tsIBBg6vX606a52Wz+bZ7P5/WwG29gxSJzhKgA6XTaDoFNF+krFAocmC//4yWEcSf2wTm7mCO19xFgSsKOLI16vV7b7XY7mRNoLwA0JymJ5uQIzgIAuX5PzDElT2m+E8BqtQ4ymcx7Yq7T6a6ZE4sKgOadTucaCwkxp1UzlEKh0GDxIXOwDWHAdi6Xe3swQDQa/Q7mywoolUpvsaptymazDWKxmBHTlWXZm405BFZoNpuGgwEmk4mE2SGtVivii4f1AO7J3ZopkQCQj7Ar1FeRChCJRJzVapX6DKNIfSc1Ax+wtQWQ55h6bH8FWDfYV4fO3wlwDr0C/BcADYiTPCxHqIEA2QsCZAkAKnRGkMbKN/sTX5YHPQ1e7SkAAAAASUVORK5CYII=';
@ -69,7 +70,9 @@ const NavWorkspaceButton = ({
></StyledLogo>
<StyledName>{currentWorkspace?.displayName ?? 'Twenty'}</StyledName>
</StyledLogoAndNameContainer>
<NavCollapseButton direction="left" show={showCollapseButton} />
{!isMobile && (
<NavCollapseButton direction="left" show={showCollapseButton} />
)}
</StyledContainer>
);
};

View File

@ -0,0 +1,55 @@
import { ReactNode } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { motion } from 'framer-motion';
import { useRecoilValue } from 'recoil';
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
import { navigationDrawerState } from '@/ui/navigation/states/navigationDrawerState';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { desktopNavDrawerWidths } from '../constants';
const StyledNavbarContainer = styled(motion.div)`
align-items: end;
display: flex;
flex-direction: column;
flex-shrink: 0;
overflow: hidden;
`;
type NavbarAnimatedContainerProps = {
children: ReactNode;
};
export const NavbarAnimatedContainer = ({
children,
}: NavbarAnimatedContainerProps) => {
const navigationDrawer = useRecoilValue(navigationDrawerState);
const isInSubMenu = useIsSettingsPage();
const theme = useTheme();
const isMobile = useIsMobile();
const desktopWidth =
navigationDrawer === ''
? 12
: isInSubMenu
? desktopNavDrawerWidths.submenu
: desktopNavDrawerWidths.menu;
return (
<StyledNavbarContainer
initial={false}
animate={{
width: !isMobile ? desktopWidth : navigationDrawer ? '100%' : 0,
opacity: navigationDrawer === '' ? 0 : 1,
}}
transition={{
duration: theme.animation.duration.normal,
}}
>
{children}
</StyledNavbarContainer>
);
};

View File

@ -1,17 +1,19 @@
import { ReactNode } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconBrandGithub } from '@/ui/display/icon';
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import packageJson from '../../../../../../package.json';
import { githubLink, leftNavbarWidth } from '../constants';
import { desktopNavDrawerWidths, githubLink } from '../constants';
import NavBackButton from './NavBackButton';
import NavItemsContainer from './NavItemsContainer';
type SubMenuNavbarProps = {
children: React.ReactNode;
children: ReactNode;
backButtonTitle: string;
displayVersion?: boolean;
};
@ -25,10 +27,11 @@ const StyledVersionContainer = styled.div`
const StyledVersion = styled.span`
color: ${({ theme }) => theme.font.color.light};
padding-left: ${({ theme }) => theme.spacing(1)};
:hover {
color: ${({ theme }) => theme.font.color.tertiary};
}
padding-left: ${({ theme }) => theme.spacing(1)};
`;
const StyledVersionLink = styled.a`
@ -36,18 +39,25 @@ const StyledVersionLink = styled.a`
color: ${({ theme }) => theme.font.color.light};
display: flex;
text-decoration: none;
:hover {
color: ${({ theme }) => theme.font.color.tertiary};
}
`;
const StyledContainer = styled.div`
box-sizing: border-box;
display: flex;
flex-direction: column;
height: 100%;
justify-content: space-between;
padding-top: ${({ theme }) => theme.spacing(9)};
width: ${() => (useIsMobile() ? '100%' : leftNavbarWidth.desktop)};
padding: ${({ theme }) => theme.spacing(2)};
padding-top: ${({ theme }) => theme.spacing(11)};
width: ${desktopNavDrawerWidths.menu};
@media (max-width: ${MOBILE_VIEWPORT}px) {
width: 100%;
}
`;
const SubMenuNavbar = ({
@ -56,13 +66,14 @@ const SubMenuNavbar = ({
displayVersion,
}: SubMenuNavbarProps) => {
const version = packageJson.version;
const isMobile = useIsMobile();
const theme = useTheme();
return (
<StyledContainer>
<div>
<NavBackButton title={backButtonTitle} />
{!isMobile && <NavBackButton title={backButtonTitle} />}
<NavItemsContainer>{children}</NavItemsContainer>
</div>
{displayVersion && (

View File

@ -18,7 +18,7 @@ import NavItem from '../NavItem';
import NavTitle from '../NavTitle';
const meta: Meta<typeof MainNavbar> = {
title: 'UI/Navigation/Navbar/MainNavbar',
title: 'UI/Navigation/NavigationDrawer/MainNavbar',
component: MainNavbar,
decorators: [SnackBarDecorator],
};

View File

@ -5,7 +5,7 @@ import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import NavCollapseButton from '../NavCollapseButton';
const meta: Meta<typeof NavCollapseButton> = {
title: 'UI/Navigation/Navbar/NavCollapseButton',
title: 'UI/Navigation/NavigationDrawer/NavCollapseButton',
component: NavCollapseButton,
};

View File

@ -9,8 +9,14 @@ import { CatalogStory } from '~/testing/types';
import NavItem from '../NavItem';
const meta: Meta<typeof NavItem> = {
title: 'UI/Navigation/Navbar/NavItem',
title: 'UI/Navigation/NavigationDrawer/NavItem',
component: NavItem,
args: {
label: 'Search',
Icon: IconSearch,
active: true,
},
argTypes: { Icon: { control: false } },
};
const StyledNavItemContainer = styled.div`
@ -28,19 +34,11 @@ const ComponentDecorator: Decorator = (Story) => (
export default meta;
type Story = StoryObj<typeof NavItem>;
export const Default: Story = {
args: {
label: 'Search',
Icon: IconSearch,
onClick: () => console.log('clicked'),
active: true,
},
argTypes: { Icon: { control: false }, onClick: { control: false } },
export const Default: Story = {
decorators: [ComponentDecorator, ComponentWithRouterDecorator],
};
export const Catalog: CatalogStory<Story, typeof NavItem> = {
args: Default.args,
decorators: [
ComponentDecorator,
CatalogDecorator,
@ -75,21 +73,28 @@ export const Catalog: CatalogStory<Story, typeof NavItem> = {
},
};
export const Soon: Story = {
export const WithSoonPill: Story = {
...Default,
args: {
...Default.args,
active: false,
soon: true,
},
argTypes: { Icon: { control: false }, onClick: { control: false } },
decorators: [ComponentDecorator, ComponentWithRouterDecorator],
};
export const Count: Story = {
export const WithCount: Story = {
...Default,
args: {
...Default.args,
count: 3,
},
argTypes: { Icon: { control: false }, onClick: { control: false } },
decorators: [ComponentDecorator, ComponentWithRouterDecorator],
};
export const WithKeyboardKeys: Story = {
...Default,
args: {
className: "hover",
keyboard: ['⌘', 'K'],
},
parameters: {
pseudo: { hover: [".hover"] },
}
};

View File

@ -14,7 +14,7 @@ import NavTitle from '../NavTitle';
import SubMenuNavbar from '../SubMenuNavbar';
const meta: Meta<typeof SubMenuNavbar> = {
title: 'UI/Navigation/Navbar/SubMenuNavbar',
title: 'UI/Navigation/NavigationDrawer/SubMenuNavbar',
component: SubMenuNavbar,
};

View File

@ -0,0 +1,6 @@
export const desktopNavDrawerWidths = {
menu: '236px',
submenu: '536px',
};
export const githubLink = 'https://github.com/twentyhq/twenty';

View File

@ -0,0 +1,6 @@
import { atom } from 'recoil';
export const navigationDrawerState = atom<'main' | 'settings' | ''>({
key: 'ui/navigationDrawerState',
default: 'main',
});

View File

@ -0,0 +1,6 @@
import { atom } from 'recoil';
export const navigationMemorizedUrlState = atom<string>({
key: 'navigationMemorizedUrlState',
default: '/',
});

View File

@ -6,16 +6,18 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useObjectRecordTable } from '@/object-record/hooks/useObjectRecordTable';
import { isFetchingMoreRecordsFamilyState } from '@/object-record/states/isFetchingMoreRecordsFamilyState';
import {
RecordTableRow,
StyledRow,
} from '@/ui/object/record-table/components/RecordTableRow';
import { RowIdContext } from '@/ui/object/record-table/contexts/RowIdContext';
import { RowIndexContext } from '@/ui/object/record-table/contexts/RowIndexContext';
import { isFetchingRecordTableDataState } from '@/ui/object/record-table/states/isFetchingRecordTableDataState';
import { isDefined } from '~/utils/isDefined';
import { RowIdContext } from '../contexts/RowIdContext';
import { RowIndexContext } from '../contexts/RowIndexContext';
import { useRecordTable } from '../hooks/useRecordTable';
import { isFetchingRecordTableDataState } from '../states/isFetchingRecordTableDataState';
import { tableRowIdsState } from '../states/tableRowIdsState';
import { RecordTableRow, StyledRow } from './RecordTableRow';
export const RecordTableBody = () => {
const { ref: lastTableRowRef, inView: lastTableRowIsVisible } = useInView();
@ -41,6 +43,7 @@ export const RecordTableBody = () => {
isFetchingRecordTableDataState,
);
// Todo, move this to an effect to not trigger many re-renders
const { fetchMoreRecords: fetchMoreObjects } = useObjectRecordTable();
useEffect(() => {