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:
@ -1,72 +0,0 @@
|
|||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { useCurrentUserTaskCount } from '@/activities/tasks/hooks/useCurrentUserDueTaskCount';
|
|
||||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
|
||||||
import { Favorites } from '@/favorites/components/Favorites';
|
|
||||||
import { ObjectMetadataNavItems } from '@/object-metadata/components/ObjectMetadataNavItems';
|
|
||||||
import { SettingsNavbar } from '@/settings/components/SettingsNavbar';
|
|
||||||
import {
|
|
||||||
IconBell,
|
|
||||||
IconCheckbox,
|
|
||||||
IconSearch,
|
|
||||||
IconSettings,
|
|
||||||
IconTargetArrow,
|
|
||||||
} from '@/ui/display/icon/index';
|
|
||||||
import { useIsSubMenuNavbarDisplayed } from '@/ui/layout/hooks/useIsSubMenuNavbarDisplayed';
|
|
||||||
import MainNavbar from '@/ui/navigation/navbar/components/MainNavbar';
|
|
||||||
import NavItem from '@/ui/navigation/navbar/components/NavItem';
|
|
||||||
import NavTitle from '@/ui/navigation/navbar/components/NavTitle';
|
|
||||||
|
|
||||||
export const AppNavbar = () => {
|
|
||||||
const currentPath = useLocation().pathname;
|
|
||||||
const { toggleCommandMenu } = useCommandMenu();
|
|
||||||
|
|
||||||
const isInSubMenu = useIsSubMenuNavbarDisplayed();
|
|
||||||
const { currentUserDueTaskCount } = useCurrentUserTaskCount();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{!isInSubMenu ? (
|
|
||||||
<MainNavbar>
|
|
||||||
<NavItem
|
|
||||||
label="Search"
|
|
||||||
Icon={IconSearch}
|
|
||||||
onClick={() => {
|
|
||||||
toggleCommandMenu();
|
|
||||||
}}
|
|
||||||
keyboard={['⌘', 'K']}
|
|
||||||
/>
|
|
||||||
<NavItem
|
|
||||||
label="Notifications"
|
|
||||||
to="/inbox"
|
|
||||||
Icon={IconBell}
|
|
||||||
soon={true}
|
|
||||||
/>
|
|
||||||
<NavItem
|
|
||||||
label="Settings"
|
|
||||||
to="/settings/profile"
|
|
||||||
Icon={IconSettings}
|
|
||||||
/>
|
|
||||||
<NavItem
|
|
||||||
label="Tasks"
|
|
||||||
to="/tasks"
|
|
||||||
active={currentPath === '/tasks'}
|
|
||||||
Icon={IconCheckbox}
|
|
||||||
count={currentUserDueTaskCount}
|
|
||||||
/>
|
|
||||||
<Favorites />
|
|
||||||
<NavTitle label="Workspace" />
|
|
||||||
<ObjectMetadataNavItems />
|
|
||||||
<NavItem
|
|
||||||
label="Opportunities"
|
|
||||||
to="/objects/opportunities"
|
|
||||||
active={currentPath === '/objects/opportunities'}
|
|
||||||
Icon={IconTargetArrow}
|
|
||||||
/>
|
|
||||||
</MainNavbar>
|
|
||||||
) : (
|
|
||||||
<SettingsNavbar />
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -2,8 +2,8 @@ import styled from '@emotion/styled';
|
|||||||
|
|
||||||
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
|
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
|
||||||
import { DraggableList } from '@/ui/layout/draggable-list/components/DraggableList';
|
import { DraggableList } from '@/ui/layout/draggable-list/components/DraggableList';
|
||||||
import NavItem from '@/ui/navigation/navbar/components/NavItem';
|
import NavItem from '@/ui/navigation/navigation-drawer/components/NavItem';
|
||||||
import NavTitle from '@/ui/navigation/navbar/components/NavTitle';
|
import NavTitle from '@/ui/navigation/navigation-drawer/components/NavTitle';
|
||||||
import { Avatar } from '@/users/components/Avatar';
|
import { Avatar } from '@/users/components/Avatar';
|
||||||
|
|
||||||
import { useFavorites } from '../hooks/useFavorites';
|
import { useFavorites } from '../hooks/useFavorites';
|
||||||
|
|||||||
@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -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}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -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,
|
||||||
|
],
|
||||||
|
};
|
||||||
@ -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,
|
||||||
|
],
|
||||||
|
};
|
||||||
4
front/src/modules/navigation/hooks/useIsSettingsPage.ts
Normal file
4
front/src/modules/navigation/hooks/useIsSettingsPage.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
export const useIsSettingsPage = () =>
|
||||||
|
useLocation().pathname.match(/\/settings\//g) !== null;
|
||||||
3
front/src/modules/navigation/hooks/useIsTasksPage.ts
Normal file
3
front/src/modules/navigation/hooks/useIsTasksPage.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
export const useIsTasksPage = () => useLocation().pathname === '/tasks';
|
||||||
@ -3,7 +3,7 @@ import { useLocation, useNavigate } from 'react-router-dom';
|
|||||||
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
|
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
|
||||||
import { Icon123 } from '@/ui/input/constants/icons';
|
import { Icon123 } from '@/ui/input/constants/icons';
|
||||||
import { useLazyLoadIcons } from '@/ui/input/hooks/useLazyLoadIcons';
|
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 = () => {
|
export const ObjectMetadataNavItems = () => {
|
||||||
const { activeObjectMetadataItems } = useObjectMetadataItemForSettings();
|
const { activeObjectMetadataItems } = useObjectMetadataItemForSettings();
|
||||||
|
|||||||
@ -89,12 +89,14 @@ export const RecordTableContainer = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<RecordTableEffect recordTableId={recordTableId} viewBarId={viewBarId} />
|
<RecordTableEffect recordTableId={recordTableId} viewBarId={viewBarId} />
|
||||||
<RecordTable
|
{
|
||||||
recordTableId={recordTableId}
|
<RecordTable
|
||||||
viewBarId={viewBarId}
|
recordTableId={recordTableId}
|
||||||
updateRecordMutation={updateEntity}
|
viewBarId={viewBarId}
|
||||||
createRecord={createRecord}
|
updateRecordMutation={updateEntity}
|
||||||
/>
|
createRecord={createRecord}
|
||||||
|
/>
|
||||||
|
}
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -12,9 +12,9 @@ import {
|
|||||||
IconUserCircle,
|
IconUserCircle,
|
||||||
IconUsers,
|
IconUsers,
|
||||||
} from '@/ui/display/icon/index';
|
} from '@/ui/display/icon/index';
|
||||||
import NavItem from '@/ui/navigation/navbar/components/NavItem';
|
import NavItem from '@/ui/navigation/navigation-drawer/components/NavItem';
|
||||||
import NavTitle from '@/ui/navigation/navbar/components/NavTitle';
|
import NavTitle from '@/ui/navigation/navigation-drawer/components/NavTitle';
|
||||||
import SubMenuNavbar from '@/ui/navigation/navbar/components/SubMenuNavbar';
|
import SubMenuNavbar from '@/ui/navigation/navigation-drawer/components/SubMenuNavbar';
|
||||||
|
|
||||||
export const SettingsNavbar = () => {
|
export const SettingsNavbar = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|||||||
@ -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 { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||||
@ -15,9 +15,7 @@ type SettingsObjectFieldDisabledActionDropdownProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const SettingsObjectFieldDisabledActionDropdown = ({
|
export const SettingsObjectFieldDisabledActionDropdown = ({
|
||||||
isCustomField,
|
|
||||||
onActivate,
|
onActivate,
|
||||||
onErase,
|
|
||||||
scopeKey,
|
scopeKey,
|
||||||
}: SettingsObjectFieldDisabledActionDropdownProps) => {
|
}: SettingsObjectFieldDisabledActionDropdownProps) => {
|
||||||
const dropdownScopeId = `${scopeKey}-settings-field-disabled-action-dropdown`;
|
const dropdownScopeId = `${scopeKey}-settings-field-disabled-action-dropdown`;
|
||||||
@ -29,10 +27,10 @@ export const SettingsObjectFieldDisabledActionDropdown = ({
|
|||||||
closeDropdown();
|
closeDropdown();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleErase = () => {
|
// const handleErase = () => {
|
||||||
onErase();
|
// onErase();
|
||||||
closeDropdown();
|
// closeDropdown();
|
||||||
};
|
// };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownScope dropdownScopeId={dropdownScopeId}>
|
<DropdownScope dropdownScopeId={dropdownScopeId}>
|
||||||
@ -48,14 +46,14 @@ export const SettingsObjectFieldDisabledActionDropdown = ({
|
|||||||
LeftIcon={IconArchiveOff}
|
LeftIcon={IconArchiveOff}
|
||||||
onClick={handleActivate}
|
onClick={handleActivate}
|
||||||
/>
|
/>
|
||||||
{isCustomField && (
|
{/* {isCustomField && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
text="Erase"
|
text="Erase"
|
||||||
accent="danger"
|
accent="danger"
|
||||||
LeftIcon={IconTrash}
|
LeftIcon={IconTrash}
|
||||||
onClick={handleErase}
|
onClick={handleErase}
|
||||||
/>
|
/>
|
||||||
)}
|
)} */}
|
||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
||||||
import { IconArchiveOff } from '@/ui/input/constants/icons';
|
import { IconArchiveOff } from '@/ui/input/constants/icons';
|
||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
@ -16,9 +16,7 @@ type SettingsObjectDisabledMenuDropDownProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const SettingsObjectDisabledMenuDropDown = ({
|
export const SettingsObjectDisabledMenuDropDown = ({
|
||||||
isCustomObject,
|
|
||||||
onActivate,
|
onActivate,
|
||||||
onErase,
|
|
||||||
scopeKey,
|
scopeKey,
|
||||||
}: SettingsObjectDisabledMenuDropDownProps) => {
|
}: SettingsObjectDisabledMenuDropDownProps) => {
|
||||||
const dropdownScopeId = `${scopeKey}-settings-object-disabled-menu-dropdown`;
|
const dropdownScopeId = `${scopeKey}-settings-object-disabled-menu-dropdown`;
|
||||||
@ -30,10 +28,10 @@ export const SettingsObjectDisabledMenuDropDown = ({
|
|||||||
closeDropdown();
|
closeDropdown();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleErase = () => {
|
// const handleErase = () => {
|
||||||
onErase();
|
// onErase();
|
||||||
closeDropdown();
|
// closeDropdown();
|
||||||
};
|
// };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownScope dropdownScopeId={dropdownScopeId}>
|
<DropdownScope dropdownScopeId={dropdownScopeId}>
|
||||||
@ -49,14 +47,14 @@ export const SettingsObjectDisabledMenuDropDown = ({
|
|||||||
LeftIcon={IconArchiveOff}
|
LeftIcon={IconArchiveOff}
|
||||||
onClick={handleActivate}
|
onClick={handleActivate}
|
||||||
/>
|
/>
|
||||||
{isCustomObject && (
|
{/* {isCustomObject && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
text="Erase"
|
text="Erase"
|
||||||
LeftIcon={IconTrash}
|
LeftIcon={IconTrash}
|
||||||
accent="danger"
|
accent="danger"
|
||||||
onClick={handleErase}
|
onClick={handleErase}
|
||||||
/>
|
/>
|
||||||
)}
|
)} */}
|
||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
export const useIsMenuNavbarDisplayed = () => {
|
||||||
|
const currentPath = useLocation().pathname;
|
||||||
|
return currentPath.match(/^\/companies(\/.*)?$/) !== null;
|
||||||
|
};
|
||||||
@ -1,6 +0,0 @@
|
|||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
|
|
||||||
export const useIsSubMenuNavbarDisplayed = () => {
|
|
||||||
const currentPath = useLocation().pathname;
|
|
||||||
return currentPath.match(/\/settings\//g) !== null;
|
|
||||||
};
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { AnimatePresence, LayoutGroup } from 'framer-motion';
|
import { AnimatePresence, LayoutGroup } from 'framer-motion';
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
|
|
||||||
import { AuthModal } from '@/auth/components/Modal';
|
import { AuthModal } from '@/auth/components/Modal';
|
||||||
import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus';
|
import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus';
|
||||||
@ -8,23 +8,22 @@ import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus';
|
|||||||
import { CommandMenu } from '@/command-menu/components/CommandMenu';
|
import { CommandMenu } from '@/command-menu/components/CommandMenu';
|
||||||
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
|
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
|
||||||
import { KeyboardShortcutMenu } from '@/keyboard-shortcut-menu/components/KeyboardShortcutMenu';
|
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 { SignInBackgroundMockPage } from '@/sign-in-background-mock/components/SignInBackgroundMockPage';
|
||||||
import { NavbarAnimatedContainer } from '@/ui/navigation/navbar/components/NavbarAnimatedContainer';
|
import { NavbarAnimatedContainer } from '@/ui/navigation/navigation-drawer/components/NavbarAnimatedContainer';
|
||||||
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
import { AppNavbar } from '~/AppNavbar';
|
|
||||||
|
|
||||||
import { isNavbarOpenedState } from '../states/isNavbarOpenedState';
|
|
||||||
|
|
||||||
const StyledLayout = styled.div`
|
const StyledLayout = styled.div`
|
||||||
background: ${({ theme }) => theme.background.noisy};
|
background: ${({ theme }) => theme.background.noisy};
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
position: relative;
|
position: relative;
|
||||||
scrollbar-color: ${({ theme }) => theme.border.color.medium};
|
scrollbar-color: ${({ theme }) => theme.border.color.medium};
|
||||||
|
|
||||||
scrollbar-width: 4px;
|
scrollbar-width: 4px;
|
||||||
width: 100vw;
|
width: 100%;
|
||||||
|
|
||||||
*::-webkit-scrollbar {
|
*::-webkit-scrollbar {
|
||||||
height: 4px;
|
height: 4px;
|
||||||
@ -41,43 +40,51 @@ const StyledLayout = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledMainContainer = styled.div`
|
const StyledPageContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledMainContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex: 0 1 100%;
|
||||||
|
flex-direction: row;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
|
||||||
width: ${() => (useRecoilValue(isNavbarOpenedState) ? '0' : '100%')};
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type DefaultLayoutProps = {
|
type DefaultLayoutProps = {
|
||||||
children: React.ReactNode;
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DefaultLayout = ({ children }: DefaultLayoutProps) => {
|
export const DefaultLayout = ({ children }: DefaultLayoutProps) => {
|
||||||
const onboardingStatus = useOnboardingStatus();
|
const onboardingStatus = useOnboardingStatus();
|
||||||
|
const isMobile = useIsMobile();
|
||||||
return (
|
return (
|
||||||
<StyledLayout>
|
<StyledLayout>
|
||||||
<CommandMenu />
|
<CommandMenu />
|
||||||
<KeyboardShortcutMenu />
|
<KeyboardShortcutMenu />
|
||||||
<NavbarAnimatedContainer>
|
<StyledPageContainer>
|
||||||
<AppNavbar />
|
<NavbarAnimatedContainer>
|
||||||
</NavbarAnimatedContainer>
|
{isMobile ? <MobileNavigationDrawer /> : <DesktopNavigationDrawer />}
|
||||||
<StyledMainContainer>
|
</NavbarAnimatedContainer>
|
||||||
{onboardingStatus && onboardingStatus !== OnboardingStatus.Completed ? (
|
<StyledMainContainer>
|
||||||
<>
|
{onboardingStatus &&
|
||||||
<SignInBackgroundMockPage />
|
onboardingStatus !== OnboardingStatus.Completed ? (
|
||||||
<AnimatePresence mode="wait">
|
<>
|
||||||
<LayoutGroup>
|
<SignInBackgroundMockPage />
|
||||||
<AuthModal>{children}</AuthModal>
|
<AnimatePresence mode="wait">
|
||||||
</LayoutGroup>
|
<LayoutGroup>
|
||||||
</AnimatePresence>
|
<AuthModal>{children}</AuthModal>
|
||||||
</>
|
</LayoutGroup>
|
||||||
) : (
|
</AnimatePresence>
|
||||||
<AppErrorBoundary>{children}</AppErrorBoundary>
|
</>
|
||||||
)}
|
) : (
|
||||||
</StyledMainContainer>
|
<AppErrorBoundary>{children}</AppErrorBoundary>
|
||||||
|
)}
|
||||||
|
</StyledMainContainer>
|
||||||
|
</StyledPageContainer>
|
||||||
|
{isMobile && <MobileNavigationBar />}
|
||||||
</StyledLayout>
|
</StyledLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,12 +1 @@
|
|||||||
import { PAGE_BAR_MIN_HEIGHT } from './PageHeader';
|
export { RightDrawerContainer as PageBody } from './RightDrawerContainer';
|
||||||
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>
|
|
||||||
);
|
|
||||||
|
|||||||
@ -1,15 +1,9 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
type PageContainerProps = {
|
|
||||||
children: JSX.Element | JSX.Element[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const PageContainer = ({ children }: PageContainerProps) => (
|
export { StyledContainer as PageContainer };
|
||||||
<StyledContainer>{children}</StyledContainer>
|
|
||||||
);
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ComponentProps, useCallback } from 'react';
|
import { ComponentProps, ReactNode } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
@ -7,15 +7,12 @@ import { useRecoilValue } from 'recoil';
|
|||||||
import { IconChevronLeft } from '@/ui/display/icon/index';
|
import { IconChevronLeft } from '@/ui/display/icon/index';
|
||||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
||||||
import { OverflowingTextWithTooltip } from '@/ui/display/tooltip/OverflowingTextWithTooltip';
|
import { OverflowingTextWithTooltip } from '@/ui/display/tooltip/OverflowingTextWithTooltip';
|
||||||
import {
|
import { IconButton } from '@/ui/input/button/components/IconButton';
|
||||||
IconButton,
|
import NavCollapseButton from '@/ui/navigation/navigation-drawer/components/NavCollapseButton';
|
||||||
IconButtonSize,
|
import { navigationDrawerState } from '@/ui/navigation/states/navigationDrawerState';
|
||||||
} from '@/ui/input/button/components/IconButton';
|
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme';
|
||||||
import NavCollapseButton from '@/ui/navigation/navbar/components/NavCollapseButton';
|
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
|
|
||||||
import { isNavbarOpenedState } from '../states/isNavbarOpenedState';
|
|
||||||
|
|
||||||
export const PAGE_BAR_MIN_HEIGHT = 40;
|
export const PAGE_BAR_MIN_HEIGHT = 40;
|
||||||
|
|
||||||
const StyledTopBarContainer = styled.div`
|
const StyledTopBarContainer = styled.div`
|
||||||
@ -31,13 +28,23 @@ const StyledTopBarContainer = styled.div`
|
|||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
padding-right: ${({ theme }) => theme.spacing(3)};
|
padding-right: ${({ theme }) => theme.spacing(3)};
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
|
|
||||||
|
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||||
|
padding-left: ${({ theme }) => theme.spacing(3)};
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledLeftContainer = styled.div`
|
const StyledLeftContainer = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
|
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||||
|
padding-left: ${({ theme }) => theme.spacing(1)};
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledTitleContainer = styled.div`
|
const StyledTitleContainer = styled.div`
|
||||||
@ -47,24 +54,15 @@ const StyledTitleContainer = styled.div`
|
|||||||
max-width: 50%;
|
max-width: 50%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledTopBarButtonContainer = styled.div`
|
|
||||||
margin-left: ${({ theme }) => theme.spacing(1)};
|
|
||||||
margin-right: ${({ theme }) => theme.spacing(1)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledBackIconButton = styled(IconButton)`
|
const StyledBackIconButton = styled(IconButton)`
|
||||||
margin-right: ${({ theme }) => theme.spacing(1)};
|
margin-right: ${({ theme }) => theme.spacing(1)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledTopBarIconStyledTitleContainer = styled.div<{
|
const StyledTopBarIconStyledTitleContainer = styled.div`
|
||||||
hideLeftPadding?: boolean;
|
|
||||||
}>`
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1 0 100%;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
padding-left: ${({ theme, hideLeftPadding }) =>
|
|
||||||
hideLeftPadding ? theme.spacing(2) : undefined};
|
|
||||||
width: 100%;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledPageActionContainer = styled.div`
|
const StyledPageActionContainer = styled.div`
|
||||||
@ -72,11 +70,16 @@ const StyledPageActionContainer = styled.div`
|
|||||||
gap: ${({ theme }) => theme.spacing(2)};
|
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'> & {
|
type PageHeaderProps = ComponentProps<'div'> & {
|
||||||
title: string;
|
title: string;
|
||||||
hasBackButton?: boolean;
|
hasBackButton?: boolean;
|
||||||
Icon: IconComponent;
|
Icon: IconComponent;
|
||||||
children?: JSX.Element | JSX.Element[];
|
children?: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PageHeader = ({
|
export const PageHeader = ({
|
||||||
@ -85,33 +88,28 @@ export const PageHeader = ({
|
|||||||
Icon,
|
Icon,
|
||||||
children,
|
children,
|
||||||
}: PageHeaderProps) => {
|
}: PageHeaderProps) => {
|
||||||
|
const isMobile = useIsMobile();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const navigateBack = useCallback(() => navigate(-1), [navigate]);
|
|
||||||
|
|
||||||
const isNavbarOpened = useRecoilValue(isNavbarOpenedState);
|
|
||||||
|
|
||||||
const iconSize: IconButtonSize = useIsMobile() ? 'small' : 'medium';
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const navigationDrawer = useRecoilValue(navigationDrawerState);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledTopBarContainer>
|
<StyledTopBarContainer>
|
||||||
<StyledLeftContainer>
|
<StyledLeftContainer>
|
||||||
{!isNavbarOpened && (
|
{navigationDrawer === '' && (
|
||||||
<StyledTopBarButtonContainer>
|
<StyledTopBarButtonContainer>
|
||||||
<NavCollapseButton direction="right" />
|
<NavCollapseButton direction="right" />
|
||||||
</StyledTopBarButtonContainer>
|
</StyledTopBarButtonContainer>
|
||||||
)}
|
)}
|
||||||
{hasBackButton && (
|
{hasBackButton && (
|
||||||
<StyledTopBarButtonContainer>
|
<StyledBackIconButton
|
||||||
<StyledBackIconButton
|
Icon={IconChevronLeft}
|
||||||
Icon={IconChevronLeft}
|
size={isMobile ? 'small' : 'medium'}
|
||||||
size={iconSize}
|
onClick={() => navigate(-1)}
|
||||||
onClick={navigateBack}
|
variant="tertiary"
|
||||||
variant="tertiary"
|
/>
|
||||||
/>
|
|
||||||
</StyledTopBarButtonContainer>
|
|
||||||
)}
|
)}
|
||||||
<StyledTopBarIconStyledTitleContainer hideLeftPadding={!hasBackButton}>
|
<StyledTopBarIconStyledTitleContainer>
|
||||||
{Icon && <Icon size={theme.icon.size.md} />}
|
{Icon && <Icon size={theme.icon.size.md} />}
|
||||||
<StyledTitleContainer data-testid="top-bar-title">
|
<StyledTitleContainer data-testid="top-bar-title">
|
||||||
<OverflowingTextWithTooltip text={title} />
|
<OverflowingTextWithTooltip text={title} />
|
||||||
|
|||||||
@ -1,25 +1,30 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { RightDrawer } from '@/ui/layout/right-drawer/components/RightDrawer';
|
import { RightDrawer } from '@/ui/layout/right-drawer/components/RightDrawer';
|
||||||
|
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme';
|
||||||
|
|
||||||
import { PagePanel } from './PagePanel';
|
import { PagePanel } from './PagePanel';
|
||||||
|
|
||||||
type RightDrawerContainerProps = {
|
type RightDrawerContainerProps = {
|
||||||
children: JSX.Element | JSX.Element[];
|
children: ReactNode;
|
||||||
topMargin?: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledMainContainer = styled.div<{ topMargin: number }>`
|
const StyledMainContainer = styled.div`
|
||||||
background: ${({ theme }) => theme.background.noisy};
|
background: ${({ theme }) => theme.background.noisy};
|
||||||
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: ${({ theme }) => theme.spacing(2)};
|
gap: ${({ theme }) => theme.spacing(2)};
|
||||||
height: calc(100% - ${(props) => props.topMargin}px);
|
height: 100%;
|
||||||
|
|
||||||
padding-bottom: ${({ theme }) => theme.spacing(3)};
|
padding-bottom: ${({ theme }) => theme.spacing(3)};
|
||||||
padding-right: ${({ 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 = {
|
type LeftContainerProps = {
|
||||||
@ -35,9 +40,8 @@ const StyledLeftContainer = styled.div<LeftContainerProps>`
|
|||||||
|
|
||||||
export const RightDrawerContainer = ({
|
export const RightDrawerContainer = ({
|
||||||
children,
|
children,
|
||||||
topMargin,
|
|
||||||
}: RightDrawerContainerProps) => (
|
}: RightDrawerContainerProps) => (
|
||||||
<StyledMainContainer topMargin={topMargin ?? 0}>
|
<StyledMainContainer>
|
||||||
<StyledLeftContainer>
|
<StyledLeftContainer>
|
||||||
<PagePanel>{children}</PagePanel>
|
<PagePanel>{children}</PagePanel>
|
||||||
</StyledLeftContainer>
|
</StyledLeftContainer>
|
||||||
|
|||||||
@ -30,7 +30,7 @@ export const SubMenuTopBarContainer = ({
|
|||||||
return (
|
return (
|
||||||
<StyledContainer isMobile={isMobile}>
|
<StyledContainer isMobile={isMobile}>
|
||||||
{isMobile && <PageHeader title={title} Icon={Icon} />}
|
{isMobile && <PageHeader title={title} Icon={Icon} />}
|
||||||
<RightDrawerContainer topMargin={16}>{children}</RightDrawerContainer>
|
<RightDrawerContainer>{children}</RightDrawerContainer>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -13,7 +13,6 @@ import {
|
|||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
import { leftNavbarWidth } from '../../../navigation/navbar/constants';
|
|
||||||
import { useRightDrawer } from '../hooks/useRightDrawer';
|
import { useRightDrawer } from '../hooks/useRightDrawer';
|
||||||
import { isRightDrawerExpandedState } from '../states/isRightDrawerExpandedState';
|
import { isRightDrawerExpandedState } from '../states/isRightDrawerExpandedState';
|
||||||
import { isRightDrawerOpenState } from '../states/isRightDrawerOpenState';
|
import { isRightDrawerOpenState } from '../states/isRightDrawerOpenState';
|
||||||
@ -70,15 +69,9 @@ export const RightDrawer = () => {
|
|||||||
|
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
const rightDrawerWidthExpanded = `calc(100% - ${
|
|
||||||
leftNavbarWidth.desktop
|
|
||||||
} - ${theme.spacing(2)})`;
|
|
||||||
|
|
||||||
const rightDrawerWidth = isRightDrawerOpen
|
const rightDrawerWidth = isRightDrawerOpen
|
||||||
? isMobile
|
? isMobile || isRightDrawerExpanded
|
||||||
? '100%'
|
? '100%'
|
||||||
: isRightDrawerExpanded
|
|
||||||
? rightDrawerWidthExpanded
|
|
||||||
: theme.rightDrawerWidth
|
: theme.rightDrawerWidth
|
||||||
: '0';
|
: '0';
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
|
||||||
});
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
import { atom } from 'recoil';
|
|
||||||
|
|
||||||
export const isNavbarSwitchingSizeState = atom({
|
|
||||||
key: 'ui/isNavbarSwitchingSizeState',
|
|
||||||
default: true,
|
|
||||||
});
|
|
||||||
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -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';
|
|
||||||
@ -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>
|
||||||
|
);
|
||||||
@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -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],
|
||||||
|
};
|
||||||
@ -10,11 +10,13 @@ type MainNavbarProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: ${({ theme }) => theme.spacing(2.5)};
|
margin-bottom: ${({ theme }) => theme.spacing(2.5)};
|
||||||
|
padding: ${({ theme }) => theme.spacing(2)};
|
||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -1,9 +1,9 @@
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { IconChevronLeft } from '@/ui/display/icon/index';
|
import { IconChevronLeft } from '@/ui/display/icon/index';
|
||||||
import { isNavbarSwitchingSizeState } from '@/ui/layout/states/isNavbarSwitchingSizeState';
|
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
||||||
|
|
||||||
type NavBackButtonProps = {
|
type NavBackButtonProps = {
|
||||||
title: string;
|
title: string;
|
||||||
@ -32,24 +32,19 @@ const StyledContainer = styled.div`
|
|||||||
|
|
||||||
const NavBackButton = ({ title }: NavBackButtonProps) => {
|
const NavBackButton = ({ title }: NavBackButtonProps) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [, setIsNavbarSwitchingSize] = useRecoilState(
|
const navigationMemorizedUrl = useRecoilValue(navigationMemorizedUrlState);
|
||||||
isNavbarSwitchingSizeState,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<StyledContainer>
|
||||||
<StyledContainer>
|
<StyledIconAndButtonContainer
|
||||||
<StyledIconAndButtonContainer
|
onClick={() => {
|
||||||
onClick={() => {
|
navigate(navigationMemorizedUrl, { replace: true });
|
||||||
setIsNavbarSwitchingSize(true);
|
}}
|
||||||
navigate('/', { replace: true });
|
>
|
||||||
}}
|
<IconChevronLeft />
|
||||||
>
|
<span>{title}</span>
|
||||||
<IconChevronLeft />
|
</StyledIconAndButtonContainer>
|
||||||
<span>{title}</span>
|
</StyledContainer>
|
||||||
</StyledIconAndButtonContainer>
|
|
||||||
</StyledContainer>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1,14 +1,14 @@
|
|||||||
import { 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 { useRecoilState } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IconLayoutSidebarLeftCollapse,
|
IconLayoutSidebarLeftCollapse,
|
||||||
IconLayoutSidebarRightCollapse,
|
IconLayoutSidebarRightCollapse,
|
||||||
} from '@/ui/display/icon';
|
} from '@/ui/display/icon';
|
||||||
import { IconButton } from '@/ui/input/button/components/IconButton';
|
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)`
|
const StyledCollapseButton = styled(motion.div)`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -41,8 +41,7 @@ const NavCollapseButton = ({
|
|||||||
direction = 'left',
|
direction = 'left',
|
||||||
show = true,
|
show = true,
|
||||||
}: NavCollapseButtonProps) => {
|
}: NavCollapseButtonProps) => {
|
||||||
const [isNavbarOpened, setIsNavbarOpened] =
|
const setNavigationDrawer = useSetRecoilState(navigationDrawerState);
|
||||||
useRecoilState(isNavbarOpenedState);
|
|
||||||
|
|
||||||
const iconSize = 'small';
|
const iconSize = 'small';
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@ -57,7 +56,11 @@ const NavCollapseButton = ({
|
|||||||
transition={{
|
transition={{
|
||||||
duration: theme.animation.duration.normal,
|
duration: theme.animation.duration.normal,
|
||||||
}}
|
}}
|
||||||
onClick={() => setIsNavbarOpened(!isNavbarOpened)}
|
onClick={() =>
|
||||||
|
setNavigationDrawer((navigationDrawer) =>
|
||||||
|
navigationDrawer === '' ? 'main' : '',
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
Icon={
|
Icon={
|
||||||
@ -1,15 +1,15 @@
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
||||||
|
import { navigationDrawerState } from '@/ui/navigation/states/navigationDrawerState';
|
||||||
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme';
|
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme';
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
|
|
||||||
import { isNavbarOpenedState } from '../../../layout/states/isNavbarOpenedState';
|
|
||||||
|
|
||||||
type NavItemProps = {
|
type NavItemProps = {
|
||||||
|
className?: string;
|
||||||
label: string;
|
label: string;
|
||||||
to?: string;
|
to?: string;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
@ -115,6 +115,7 @@ const StyledKeyBoardShortcut = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const NavItem = ({
|
const NavItem = ({
|
||||||
|
className,
|
||||||
label,
|
label,
|
||||||
Icon,
|
Icon,
|
||||||
to,
|
to,
|
||||||
@ -126,25 +127,26 @@ const NavItem = ({
|
|||||||
keyboard,
|
keyboard,
|
||||||
}: NavItemProps) => {
|
}: NavItemProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const navigate = useNavigate();
|
|
||||||
const [, setIsNavbarOpened] = useRecoilState(isNavbarOpenedState);
|
|
||||||
|
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const setNavigationDrawer = useSetRecoilState(navigationDrawerState);
|
||||||
|
|
||||||
const handleItemClick = () => {
|
const handleItemClick = () => {
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
setIsNavbarOpened(false);
|
setNavigationDrawer('');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onClick) {
|
if (onClick) {
|
||||||
onClick();
|
onClick();
|
||||||
} else if (to) {
|
return;
|
||||||
navigate(to);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (to) navigate(to);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledItem
|
<StyledItem
|
||||||
|
className={className}
|
||||||
onClick={handleItemClick}
|
onClick={handleItemClick}
|
||||||
active={active}
|
active={active}
|
||||||
aria-selected={active}
|
aria-selected={active}
|
||||||
@ -157,7 +159,7 @@ const NavItem = ({
|
|||||||
{!!count && <StyledItemCount>{count}</StyledItemCount>}
|
{!!count && <StyledItemCount>{count}</StyledItemCount>}
|
||||||
{keyboard && (
|
{keyboard && (
|
||||||
<StyledKeyBoardShortcut className="keyboard-shortcuts">
|
<StyledKeyBoardShortcut className="keyboard-shortcuts">
|
||||||
{keyboard.map((key) => key)}
|
{keyboard}
|
||||||
</StyledKeyBoardShortcut>
|
</StyledKeyBoardShortcut>
|
||||||
)}
|
)}
|
||||||
</StyledItem>
|
</StyledItem>
|
||||||
@ -2,6 +2,7 @@ import styled from '@emotion/styled';
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||||
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
import { getImageAbsoluteURIOrBase64 } from '@/users/utils/getProfilePictureAbsoluteURI';
|
import { getImageAbsoluteURIOrBase64 } from '@/users/utils/getProfilePictureAbsoluteURI';
|
||||||
|
|
||||||
import NavCollapseButton from './NavCollapseButton';
|
import NavCollapseButton from './NavCollapseButton';
|
||||||
@ -53,7 +54,7 @@ const NavWorkspaceButton = ({
|
|||||||
showCollapseButton,
|
showCollapseButton,
|
||||||
}: NavWorkspaceButtonProps) => {
|
}: NavWorkspaceButtonProps) => {
|
||||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||||
|
const isMobile = useIsMobile();
|
||||||
const DEFAULT_LOGO =
|
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=';
|
'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>
|
></StyledLogo>
|
||||||
<StyledName>{currentWorkspace?.displayName ?? 'Twenty'}</StyledName>
|
<StyledName>{currentWorkspace?.displayName ?? 'Twenty'}</StyledName>
|
||||||
</StyledLogoAndNameContainer>
|
</StyledLogoAndNameContainer>
|
||||||
<NavCollapseButton direction="left" show={showCollapseButton} />
|
{!isMobile && (
|
||||||
|
<NavCollapseButton direction="left" show={showCollapseButton} />
|
||||||
|
)}
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,17 +1,19 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { IconBrandGithub } from '@/ui/display/icon';
|
import { IconBrandGithub } from '@/ui/display/icon';
|
||||||
|
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme';
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
|
|
||||||
import packageJson from '../../../../../../package.json';
|
import packageJson from '../../../../../../package.json';
|
||||||
import { githubLink, leftNavbarWidth } from '../constants';
|
import { desktopNavDrawerWidths, githubLink } from '../constants';
|
||||||
|
|
||||||
import NavBackButton from './NavBackButton';
|
import NavBackButton from './NavBackButton';
|
||||||
import NavItemsContainer from './NavItemsContainer';
|
import NavItemsContainer from './NavItemsContainer';
|
||||||
|
|
||||||
type SubMenuNavbarProps = {
|
type SubMenuNavbarProps = {
|
||||||
children: React.ReactNode;
|
children: ReactNode;
|
||||||
backButtonTitle: string;
|
backButtonTitle: string;
|
||||||
displayVersion?: boolean;
|
displayVersion?: boolean;
|
||||||
};
|
};
|
||||||
@ -25,10 +27,11 @@ const StyledVersionContainer = styled.div`
|
|||||||
|
|
||||||
const StyledVersion = styled.span`
|
const StyledVersion = styled.span`
|
||||||
color: ${({ theme }) => theme.font.color.light};
|
color: ${({ theme }) => theme.font.color.light};
|
||||||
|
padding-left: ${({ theme }) => theme.spacing(1)};
|
||||||
|
|
||||||
:hover {
|
:hover {
|
||||||
color: ${({ theme }) => theme.font.color.tertiary};
|
color: ${({ theme }) => theme.font.color.tertiary};
|
||||||
}
|
}
|
||||||
padding-left: ${({ theme }) => theme.spacing(1)};
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledVersionLink = styled.a`
|
const StyledVersionLink = styled.a`
|
||||||
@ -36,18 +39,25 @@ const StyledVersionLink = styled.a`
|
|||||||
color: ${({ theme }) => theme.font.color.light};
|
color: ${({ theme }) => theme.font.color.light};
|
||||||
display: flex;
|
display: flex;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
:hover {
|
:hover {
|
||||||
color: ${({ theme }) => theme.font.color.tertiary};
|
color: ${({ theme }) => theme.font.color.tertiary};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding-top: ${({ theme }) => theme.spacing(9)};
|
padding: ${({ theme }) => theme.spacing(2)};
|
||||||
width: ${() => (useIsMobile() ? '100%' : leftNavbarWidth.desktop)};
|
padding-top: ${({ theme }) => theme.spacing(11)};
|
||||||
|
width: ${desktopNavDrawerWidths.menu};
|
||||||
|
|
||||||
|
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const SubMenuNavbar = ({
|
const SubMenuNavbar = ({
|
||||||
@ -56,13 +66,14 @@ const SubMenuNavbar = ({
|
|||||||
displayVersion,
|
displayVersion,
|
||||||
}: SubMenuNavbarProps) => {
|
}: SubMenuNavbarProps) => {
|
||||||
const version = packageJson.version;
|
const version = packageJson.version;
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<div>
|
<div>
|
||||||
<NavBackButton title={backButtonTitle} />
|
{!isMobile && <NavBackButton title={backButtonTitle} />}
|
||||||
<NavItemsContainer>{children}</NavItemsContainer>
|
<NavItemsContainer>{children}</NavItemsContainer>
|
||||||
</div>
|
</div>
|
||||||
{displayVersion && (
|
{displayVersion && (
|
||||||
@ -18,7 +18,7 @@ import NavItem from '../NavItem';
|
|||||||
import NavTitle from '../NavTitle';
|
import NavTitle from '../NavTitle';
|
||||||
|
|
||||||
const meta: Meta<typeof MainNavbar> = {
|
const meta: Meta<typeof MainNavbar> = {
|
||||||
title: 'UI/Navigation/Navbar/MainNavbar',
|
title: 'UI/Navigation/NavigationDrawer/MainNavbar',
|
||||||
component: MainNavbar,
|
component: MainNavbar,
|
||||||
decorators: [SnackBarDecorator],
|
decorators: [SnackBarDecorator],
|
||||||
};
|
};
|
||||||
@ -5,7 +5,7 @@ import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
|||||||
import NavCollapseButton from '../NavCollapseButton';
|
import NavCollapseButton from '../NavCollapseButton';
|
||||||
|
|
||||||
const meta: Meta<typeof NavCollapseButton> = {
|
const meta: Meta<typeof NavCollapseButton> = {
|
||||||
title: 'UI/Navigation/Navbar/NavCollapseButton',
|
title: 'UI/Navigation/NavigationDrawer/NavCollapseButton',
|
||||||
component: NavCollapseButton,
|
component: NavCollapseButton,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -9,8 +9,14 @@ import { CatalogStory } from '~/testing/types';
|
|||||||
import NavItem from '../NavItem';
|
import NavItem from '../NavItem';
|
||||||
|
|
||||||
const meta: Meta<typeof NavItem> = {
|
const meta: Meta<typeof NavItem> = {
|
||||||
title: 'UI/Navigation/Navbar/NavItem',
|
title: 'UI/Navigation/NavigationDrawer/NavItem',
|
||||||
component: NavItem,
|
component: NavItem,
|
||||||
|
args: {
|
||||||
|
label: 'Search',
|
||||||
|
Icon: IconSearch,
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
argTypes: { Icon: { control: false } },
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledNavItemContainer = styled.div`
|
const StyledNavItemContainer = styled.div`
|
||||||
@ -28,19 +34,11 @@ const ComponentDecorator: Decorator = (Story) => (
|
|||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof NavItem>;
|
type Story = StoryObj<typeof NavItem>;
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
|
||||||
label: 'Search',
|
|
||||||
Icon: IconSearch,
|
|
||||||
onClick: () => console.log('clicked'),
|
|
||||||
active: true,
|
|
||||||
},
|
|
||||||
argTypes: { Icon: { control: false }, onClick: { control: false } },
|
|
||||||
decorators: [ComponentDecorator, ComponentWithRouterDecorator],
|
decorators: [ComponentDecorator, ComponentWithRouterDecorator],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Catalog: CatalogStory<Story, typeof NavItem> = {
|
export const Catalog: CatalogStory<Story, typeof NavItem> = {
|
||||||
args: Default.args,
|
|
||||||
decorators: [
|
decorators: [
|
||||||
ComponentDecorator,
|
ComponentDecorator,
|
||||||
CatalogDecorator,
|
CatalogDecorator,
|
||||||
@ -75,21 +73,28 @@ export const Catalog: CatalogStory<Story, typeof NavItem> = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Soon: Story = {
|
export const WithSoonPill: Story = {
|
||||||
|
...Default,
|
||||||
args: {
|
args: {
|
||||||
...Default.args,
|
|
||||||
active: false,
|
active: false,
|
||||||
soon: true,
|
soon: true,
|
||||||
},
|
},
|
||||||
argTypes: { Icon: { control: false }, onClick: { control: false } },
|
|
||||||
decorators: [ComponentDecorator, ComponentWithRouterDecorator],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Count: Story = {
|
export const WithCount: Story = {
|
||||||
|
...Default,
|
||||||
args: {
|
args: {
|
||||||
...Default.args,
|
|
||||||
count: 3,
|
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"] },
|
||||||
|
}
|
||||||
};
|
};
|
||||||
@ -14,7 +14,7 @@ import NavTitle from '../NavTitle';
|
|||||||
import SubMenuNavbar from '../SubMenuNavbar';
|
import SubMenuNavbar from '../SubMenuNavbar';
|
||||||
|
|
||||||
const meta: Meta<typeof SubMenuNavbar> = {
|
const meta: Meta<typeof SubMenuNavbar> = {
|
||||||
title: 'UI/Navigation/Navbar/SubMenuNavbar',
|
title: 'UI/Navigation/NavigationDrawer/SubMenuNavbar',
|
||||||
component: SubMenuNavbar,
|
component: SubMenuNavbar,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
export const desktopNavDrawerWidths = {
|
||||||
|
menu: '236px',
|
||||||
|
submenu: '536px',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const githubLink = 'https://github.com/twentyhq/twenty';
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { atom } from 'recoil';
|
||||||
|
|
||||||
|
export const navigationDrawerState = atom<'main' | 'settings' | ''>({
|
||||||
|
key: 'ui/navigationDrawerState',
|
||||||
|
default: 'main',
|
||||||
|
});
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { atom } from 'recoil';
|
||||||
|
|
||||||
|
export const navigationMemorizedUrlState = atom<string>({
|
||||||
|
key: 'navigationMemorizedUrlState',
|
||||||
|
default: '/',
|
||||||
|
});
|
||||||
@ -6,16 +6,18 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
|||||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
||||||
import { useObjectRecordTable } from '@/object-record/hooks/useObjectRecordTable';
|
import { useObjectRecordTable } from '@/object-record/hooks/useObjectRecordTable';
|
||||||
import { isFetchingMoreRecordsFamilyState } from '@/object-record/states/isFetchingMoreRecordsFamilyState';
|
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 { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
import { RowIdContext } from '../contexts/RowIdContext';
|
|
||||||
import { RowIndexContext } from '../contexts/RowIndexContext';
|
|
||||||
import { useRecordTable } from '../hooks/useRecordTable';
|
import { useRecordTable } from '../hooks/useRecordTable';
|
||||||
import { isFetchingRecordTableDataState } from '../states/isFetchingRecordTableDataState';
|
|
||||||
import { tableRowIdsState } from '../states/tableRowIdsState';
|
import { tableRowIdsState } from '../states/tableRowIdsState';
|
||||||
|
|
||||||
import { RecordTableRow, StyledRow } from './RecordTableRow';
|
|
||||||
|
|
||||||
export const RecordTableBody = () => {
|
export const RecordTableBody = () => {
|
||||||
const { ref: lastTableRowRef, inView: lastTableRowIsVisible } = useInView();
|
const { ref: lastTableRowRef, inView: lastTableRowIsVisible } = useInView();
|
||||||
|
|
||||||
@ -41,6 +43,7 @@ export const RecordTableBody = () => {
|
|||||||
isFetchingRecordTableDataState,
|
isFetchingRecordTableDataState,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Todo, move this to an effect to not trigger many re-renders
|
||||||
const { fetchMoreRecords: fetchMoreObjects } = useObjectRecordTable();
|
const { fetchMoreRecords: fetchMoreObjects } = useObjectRecordTable();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
/* eslint-disable no-console */
|
|
||||||
import afterFrame from 'afterframe';
|
|
||||||
|
|
||||||
export const measureTotalFrameLoad = (id: string) => {
|
|
||||||
const timerId = `Total loading time for : ${id}`;
|
|
||||||
|
|
||||||
console.time(timerId);
|
|
||||||
|
|
||||||
afterFrame(() => {
|
|
||||||
console.timeEnd(timerId);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user