feat - Compact sidebar (#7414)
This commit is contained in:
@ -8,7 +8,7 @@ import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
|
||||
import { OBJECT_SETTINGS_WIDTH } from '@/settings/data-model/constants/ObjectSettings';
|
||||
import { SignInBackgroundMockPage } from '@/sign-in-background-mock/components/SignInBackgroundMockPage';
|
||||
import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal';
|
||||
import { DESKTOP_NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/DesktopNavDrawerWidths';
|
||||
import { NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/NavDrawerWidths';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { useScreenSize } from '@/ui/utilities/screen-size/hooks/useScreenSize';
|
||||
import { css, Global, useTheme } from '@emotion/react';
|
||||
@ -84,7 +84,7 @@ export const DefaultLayout = () => {
|
||||
isSettingsPage && !isMobile
|
||||
? (windowsWidth -
|
||||
(OBJECT_SETTINGS_WIDTH +
|
||||
DESKTOP_NAV_DRAWER_WIDTHS.menu +
|
||||
NAV_DRAWER_WIDTHS.menu.desktop.expanded +
|
||||
64)) /
|
||||
2
|
||||
: 0,
|
||||
|
||||
@ -13,7 +13,8 @@ import {
|
||||
|
||||
import { IconButton } from '@/ui/input/button/components/IconButton';
|
||||
import { NavigationDrawerCollapseButton } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerCollapseButton';
|
||||
import { isNavigationDrawerOpenState } from '@/ui/navigation/states/isNavigationDrawerOpenState';
|
||||
|
||||
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
|
||||
export const PAGE_BAR_MIN_HEIGHT = 40;
|
||||
@ -108,12 +109,14 @@ export const PageHeader = ({
|
||||
}: PageHeaderProps) => {
|
||||
const isMobile = useIsMobile();
|
||||
const theme = useTheme();
|
||||
const isNavigationDrawerOpen = useRecoilValue(isNavigationDrawerOpenState);
|
||||
const isNavigationDrawerExpanded = useRecoilValue(
|
||||
isNavigationDrawerExpandedState,
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledTopBarContainer width={width}>
|
||||
<StyledLeftContainer>
|
||||
{!isMobile && !isNavigationDrawerOpen && (
|
||||
{!isMobile && !isNavigationDrawerExpanded && (
|
||||
<StyledTopBarButtonContainer>
|
||||
<NavigationDrawerCollapseButton direction="right" />
|
||||
</StyledTopBarButtonContainer>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { IconChevronDown } from 'twenty-ui';
|
||||
|
||||
@ -15,6 +15,7 @@ import { MULTI_WORKSPACE_DROPDOWN_ID } from '@/ui/navigation/navigation-drawer/c
|
||||
import { useWorkspaceSwitching } from '@/ui/navigation/navigation-drawer/hooks/useWorkspaceSwitching';
|
||||
import { NavigationDrawerHotKeyScope } from '@/ui/navigation/navigation-drawer/types/NavigationDrawerHotKeyScope';
|
||||
import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI';
|
||||
import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper';
|
||||
|
||||
const StyledLogo = styled.div<{ logo: string }>`
|
||||
background: url(${({ logo }) => logo});
|
||||
@ -37,8 +38,6 @@ const StyledContainer = styled.div`
|
||||
padding: calc(${({ theme }) => theme.spacing(1)} - 1px);
|
||||
width: 100%;
|
||||
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
@ -48,6 +47,7 @@ const StyledContainer = styled.div`
|
||||
const StyledLabel = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin: 0 ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledIconChevronDown = styled(IconChevronDown)<{ disabled?: boolean }>`
|
||||
@ -95,11 +95,15 @@ export const MultiWorkspaceDropdownButton = ({
|
||||
) ?? ''
|
||||
}
|
||||
/>
|
||||
<StyledLabel>{currentWorkspace?.displayName ?? ''}</StyledLabel>
|
||||
<StyledIconChevronDown
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
<NavigationDrawerAnimatedCollapseWrapper>
|
||||
<StyledLabel>{currentWorkspace?.displayName ?? ''}</StyledLabel>
|
||||
</NavigationDrawerAnimatedCollapseWrapper>
|
||||
<NavigationDrawerAnimatedCollapseWrapper>
|
||||
<StyledIconChevronDown
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
</NavigationDrawerAnimatedCollapseWrapper>
|
||||
</StyledContainer>
|
||||
}
|
||||
dropdownComponents={
|
||||
|
||||
@ -1,49 +1,45 @@
|
||||
import { css, useTheme } from '@emotion/react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { motion } from 'framer-motion';
|
||||
import { ReactNode, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { MOBILE_VIEWPORT } from 'twenty-ui';
|
||||
|
||||
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
|
||||
import { isNavigationDrawerOpenState } from '@/ui/navigation/states/isNavigationDrawerOpenState';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
|
||||
import { DESKTOP_NAV_DRAWER_WIDTHS } from '../constants/DesktopNavDrawerWidths';
|
||||
import { NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/NavDrawerWidths';
|
||||
|
||||
import { isNavigationDrawerExpandedState } from '../../states/isNavigationDrawerExpanded';
|
||||
import { NavigationDrawerBackButton } from './NavigationDrawerBackButton';
|
||||
import { NavigationDrawerHeader } from './NavigationDrawerHeader';
|
||||
import { useIsSettingsDrawer } from '@/navigation/hooks/useIsSettingsDrawer';
|
||||
|
||||
export type NavigationDrawerProps = {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
footer?: ReactNode;
|
||||
isSubMenu?: boolean;
|
||||
logo?: string;
|
||||
title?: string;
|
||||
};
|
||||
|
||||
const StyledAnimatedContainer = styled(motion.div)`
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
`;
|
||||
const StyledAnimatedContainer = styled(motion.div)``;
|
||||
|
||||
const StyledContainer = styled.div<{ isSubMenu?: boolean }>`
|
||||
const StyledContainer = styled.div<{
|
||||
isSettings?: boolean;
|
||||
isMobile?: boolean;
|
||||
}>`
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: ${NAV_DRAWER_WIDTHS.menu.desktop.expanded}px;
|
||||
gap: ${({ theme }) => theme.spacing(3)};
|
||||
height: 100%;
|
||||
min-width: ${DESKTOP_NAV_DRAWER_WIDTHS.menu}px;
|
||||
padding: ${({ theme }) => theme.spacing(3, 2, 4)};
|
||||
|
||||
${({ isSubMenu, theme }) =>
|
||||
isSubMenu
|
||||
? css`
|
||||
padding-left: ${theme.spacing(0)};
|
||||
padding-right: ${theme.spacing(8)};
|
||||
`
|
||||
: ''}
|
||||
padding: ${({ theme, isSettings, isMobile }) =>
|
||||
isSettings
|
||||
? isMobile
|
||||
? theme.spacing(3, 8)
|
||||
: theme.spacing(3, 8, 4, 0)
|
||||
: theme.spacing(3, 2, 4)};
|
||||
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
width: 100%;
|
||||
@ -61,15 +57,16 @@ export const NavigationDrawer = ({
|
||||
children,
|
||||
className,
|
||||
footer,
|
||||
isSubMenu,
|
||||
logo,
|
||||
title,
|
||||
}: NavigationDrawerProps) => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
const isSettingsDrawer = useIsSettingsDrawer();
|
||||
const theme = useTheme();
|
||||
const isNavigationDrawerOpen = useRecoilValue(isNavigationDrawerOpenState);
|
||||
const isSettingsPage = useIsSettingsPage();
|
||||
const isNavigationDrawerExpanded = useRecoilValue(
|
||||
isNavigationDrawerExpandedState,
|
||||
);
|
||||
|
||||
const handleHover = () => {
|
||||
setIsHovered(true);
|
||||
@ -79,30 +76,35 @@ export const NavigationDrawer = ({
|
||||
setIsHovered(false);
|
||||
};
|
||||
|
||||
const desktopWidth = !isNavigationDrawerOpen
|
||||
? 12
|
||||
: DESKTOP_NAV_DRAWER_WIDTHS.menu;
|
||||
const desktopWidth = isNavigationDrawerExpanded
|
||||
? NAV_DRAWER_WIDTHS.menu.desktop.expanded
|
||||
: NAV_DRAWER_WIDTHS.menu.desktop.collapsed;
|
||||
|
||||
const mobileWidth = isNavigationDrawerOpen ? '100%' : 0;
|
||||
const mobileWidth = isNavigationDrawerExpanded
|
||||
? NAV_DRAWER_WIDTHS.menu.mobile.expanded
|
||||
: NAV_DRAWER_WIDTHS.menu.mobile.collapsed;
|
||||
|
||||
const navigationDrawerAnimate = {
|
||||
width: isMobile ? mobileWidth : desktopWidth,
|
||||
opacity: isNavigationDrawerExpanded || !isSettingsDrawer ? 1 : 0,
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledAnimatedContainer
|
||||
className={className}
|
||||
initial={false}
|
||||
animate={{
|
||||
width: isMobile ? mobileWidth : desktopWidth,
|
||||
opacity: isNavigationDrawerOpen || isSettingsPage ? 1 : 0,
|
||||
}}
|
||||
animate={navigationDrawerAnimate}
|
||||
transition={{
|
||||
duration: theme.animation.duration.normal,
|
||||
}}
|
||||
>
|
||||
<StyledContainer
|
||||
isSubMenu={isSubMenu}
|
||||
isSettings={isSettingsDrawer}
|
||||
isMobile={isMobile}
|
||||
onMouseEnter={handleHover}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{isSubMenu && title ? (
|
||||
{isSettingsDrawer && title ? (
|
||||
!isMobile && <NavigationDrawerBackButton title={title} />
|
||||
) : (
|
||||
<NavigationDrawerHeader
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { AnimationControls, motion, TargetAndTransition } from 'framer-motion';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
|
||||
|
||||
const StyledAnimatedContainer = styled(motion.div)``;
|
||||
|
||||
export const NavigationDrawerAnimatedCollapseWrapper = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isSettingsPage = useIsSettingsPage();
|
||||
const isNavigationDrawerExpanded = useRecoilValue(
|
||||
isNavigationDrawerExpandedState,
|
||||
);
|
||||
|
||||
if (isSettingsPage) {
|
||||
return children;
|
||||
}
|
||||
|
||||
const animate: AnimationControls | TargetAndTransition =
|
||||
isNavigationDrawerExpanded
|
||||
? {
|
||||
opacity: 1,
|
||||
width: 'auto',
|
||||
height: 'auto',
|
||||
pointerEvents: 'auto',
|
||||
}
|
||||
: {
|
||||
opacity: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
pointerEvents: 'none',
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledAnimatedContainer
|
||||
initial={false}
|
||||
animate={animate}
|
||||
transition={{
|
||||
duration: theme.animation.duration.normal,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</StyledAnimatedContainer>
|
||||
);
|
||||
};
|
||||
@ -1,9 +1,11 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { IconX } from 'twenty-ui';
|
||||
|
||||
import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink';
|
||||
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||
import { navigationDrawerExpandedMemorizedState } from '@/ui/navigation/states/navigationDrawerExpandedMemorizedState';
|
||||
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
||||
|
||||
type NavigationDrawerBackButtonProps = {
|
||||
@ -43,9 +45,22 @@ export const NavigationDrawerBackButton = ({
|
||||
const theme = useTheme();
|
||||
const navigationMemorizedUrl = useRecoilValue(navigationMemorizedUrlState);
|
||||
|
||||
const setIsNavigationDrawerExpanded = useSetRecoilState(
|
||||
isNavigationDrawerExpandedState,
|
||||
);
|
||||
const navigationDrawerExpandedMemorized = useRecoilValue(
|
||||
navigationDrawerExpandedMemorizedState,
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<UndecoratedLink to={navigationMemorizedUrl} replace>
|
||||
<UndecoratedLink
|
||||
to={navigationMemorizedUrl}
|
||||
replace
|
||||
onClick={() =>
|
||||
setIsNavigationDrawerExpanded(navigationDrawerExpandedMemorized)
|
||||
}
|
||||
>
|
||||
<StyledIconAndButtonContainer>
|
||||
<IconX
|
||||
size={theme.icon.size.md}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { IconButton } from '@/ui/input/button/components/IconButton';
|
||||
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||
import styled from '@emotion/styled';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import {
|
||||
@ -5,9 +7,6 @@ import {
|
||||
IconLayoutSidebarRightCollapse,
|
||||
} from 'twenty-ui';
|
||||
|
||||
import { IconButton } from '@/ui/input/button/components/IconButton';
|
||||
import { isNavigationDrawerOpenState } from '@/ui/navigation/states/isNavigationDrawerOpenState';
|
||||
|
||||
const StyledCollapseButton = styled.div`
|
||||
align-items: center;
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
@ -33,15 +32,17 @@ export const NavigationDrawerCollapseButton = ({
|
||||
className,
|
||||
direction = 'left',
|
||||
}: NavigationDrawerCollapseButtonProps) => {
|
||||
const setIsNavigationDrawerOpen = useSetRecoilState(
|
||||
isNavigationDrawerOpenState,
|
||||
const setIsNavigationDrawerExpanded = useSetRecoilState(
|
||||
isNavigationDrawerExpandedState,
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledCollapseButton
|
||||
className={className}
|
||||
onClick={() =>
|
||||
setIsNavigationDrawerOpen((previousIsOpen) => !previousIsOpen)
|
||||
setIsNavigationDrawerExpanded(
|
||||
(previousIsExpanded) => !previousIsExpanded,
|
||||
)
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
|
||||
@ -7,17 +7,20 @@ import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/consta
|
||||
import { DEFAULT_WORKSPACE_NAME } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceName';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
|
||||
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { NavigationDrawerCollapseButton } from './NavigationDrawerCollapseButton';
|
||||
import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper';
|
||||
|
||||
const StyledContainer = styled.div<{ isMultiWorkspace: boolean }>`
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${({ theme, isMultiWorkspace }) =>
|
||||
!isMultiWorkspace ? theme.spacing(2) : null};
|
||||
height: ${({ theme }) => theme.spacing(8)};
|
||||
user-select: none;
|
||||
`;
|
||||
const StyledSingleWorkspaceContainer = styled(StyledContainer)`
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledLogo = styled.div<{ logo: string }>`
|
||||
background: url(${({ logo }) => logo});
|
||||
@ -57,21 +60,25 @@ export const NavigationDrawerHeader = ({
|
||||
const isMobile = useIsMobile();
|
||||
const workspaces = useRecoilValue(workspacesState);
|
||||
const isMultiWorkspace = workspaces !== null && workspaces.length > 1;
|
||||
const isNavigationDrawerExpanded = useRecoilValue(
|
||||
isNavigationDrawerExpandedState,
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledContainer isMultiWorkspace={isMultiWorkspace}>
|
||||
<StyledContainer>
|
||||
{isMultiWorkspace ? (
|
||||
<MultiWorkspaceDropdownButton workspaces={workspaces} />
|
||||
) : (
|
||||
<>
|
||||
<StyledSingleWorkspaceContainer>
|
||||
<StyledLogo
|
||||
logo={isNonEmptyString(logo) ? logo : DEFAULT_WORKSPACE_LOGO}
|
||||
/>
|
||||
<StyledName>{name}</StyledName>
|
||||
</>
|
||||
<NavigationDrawerAnimatedCollapseWrapper>
|
||||
<StyledName>{name}</StyledName>
|
||||
</NavigationDrawerAnimatedCollapseWrapper>
|
||||
</StyledSingleWorkspaceContainer>
|
||||
)}
|
||||
|
||||
{!isMobile && (
|
||||
{!isMobile && isNavigationDrawerExpanded && (
|
||||
<StyledNavigationDrawerCollapseButton
|
||||
direction="left"
|
||||
show={showCollapseButton}
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
import { NavigationDrawerItemBreadcrumb } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemBreadcrumb';
|
||||
import { NavigationDrawerSubItemState } from '@/ui/navigation/navigation-drawer/types/NavigationDrawerSubItemState';
|
||||
import { isNavigationDrawerOpenState } from '@/ui/navigation/states/isNavigationDrawerOpenState';
|
||||
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper';
|
||||
import isPropValid from '@emotion/is-prop-valid';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import {
|
||||
IconComponent,
|
||||
MOBILE_VIEWPORT,
|
||||
@ -15,6 +16,8 @@ import {
|
||||
TablerIconsProps,
|
||||
} from 'twenty-ui';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
|
||||
import { NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/NavDrawerWidths';
|
||||
|
||||
const DEFAULT_INDENTATION_LEVEL = 1;
|
||||
|
||||
@ -38,7 +41,7 @@ export type NavigationDrawerItemProps = {
|
||||
type StyledItemProps = Pick<
|
||||
NavigationDrawerItemProps,
|
||||
'active' | 'danger' | 'indentationLevel' | 'soon' | 'to'
|
||||
>;
|
||||
> & { isNavigationDrawerExpanded: boolean };
|
||||
|
||||
const StyledItem = styled('div', {
|
||||
shouldForwardProp: (prop) =>
|
||||
@ -65,9 +68,8 @@ const StyledItem = styled('div', {
|
||||
}};
|
||||
cursor: ${(props) => (props.soon ? 'default' : 'pointer')};
|
||||
display: flex;
|
||||
font-family: 'Inter';
|
||||
font-family: ${({ theme }) => theme.font.family};
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
|
||||
padding-bottom: ${({ theme }) => theme.spacing(1)};
|
||||
padding-left: ${({ theme }) => theme.spacing(1)};
|
||||
@ -78,7 +80,12 @@ const StyledItem = styled('div', {
|
||||
indentationLevel === 2 ? '2px' : '0'};
|
||||
|
||||
pointer-events: ${(props) => (props.soon ? 'none' : 'auto')};
|
||||
width: 100%;
|
||||
|
||||
width: ${(props) =>
|
||||
!props.isNavigationDrawerExpanded
|
||||
? `${NAV_DRAWER_WIDTHS.menu.desktop.collapsed - 24}px`
|
||||
: '100%'};
|
||||
|
||||
:hover {
|
||||
background: ${({ theme }) => theme.background.transparent.light};
|
||||
color: ${(props) =>
|
||||
@ -96,9 +103,14 @@ const StyledItem = styled('div', {
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledItemElementsContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledItemLabel = styled.div`
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
@ -111,7 +123,6 @@ const StyledItemCount = styled.div`
|
||||
display: flex;
|
||||
font-size: ${({ theme }) => theme.font.size.xs};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
|
||||
height: 16px;
|
||||
justify-content: center;
|
||||
margin-left: auto;
|
||||
@ -151,16 +162,15 @@ export const NavigationDrawerItem = ({
|
||||
}: NavigationDrawerItemProps) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useIsMobile();
|
||||
const isSettingsPage = useIsSettingsPage();
|
||||
const navigate = useNavigate();
|
||||
const setIsNavigationDrawerOpen = useSetRecoilState(
|
||||
isNavigationDrawerOpenState,
|
||||
);
|
||||
|
||||
const [isNavigationDrawerExpanded, setIsNavigationDrawerExpanded] =
|
||||
useRecoilState(isNavigationDrawerExpandedState);
|
||||
const showBreadcrumb = indentationLevel === 2;
|
||||
|
||||
const handleItemClick = () => {
|
||||
if (isMobile) {
|
||||
setIsNavigationDrawerOpen(false);
|
||||
setIsNavigationDrawerExpanded(false);
|
||||
}
|
||||
|
||||
if (isDefined(onClick)) {
|
||||
@ -185,25 +195,51 @@ export const NavigationDrawerItem = ({
|
||||
as={to ? Link : 'div'}
|
||||
to={to ? to : undefined}
|
||||
indentationLevel={indentationLevel}
|
||||
isNavigationDrawerExpanded={isNavigationDrawerExpanded}
|
||||
>
|
||||
{showBreadcrumb && (
|
||||
<NavigationDrawerItemBreadcrumb state={subItemState} />
|
||||
)}
|
||||
{Icon && (
|
||||
<Icon
|
||||
style={{ minWidth: theme.icon.size.md }}
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.md}
|
||||
/>
|
||||
)}
|
||||
<StyledItemLabel>{label}</StyledItemLabel>
|
||||
{soon && <Pill label="Soon" />}
|
||||
{!!count && <StyledItemCount>{count}</StyledItemCount>}
|
||||
{keyboard && (
|
||||
<StyledKeyBoardShortcut className="keyboard-shortcuts">
|
||||
{keyboard}
|
||||
</StyledKeyBoardShortcut>
|
||||
<NavigationDrawerAnimatedCollapseWrapper>
|
||||
<NavigationDrawerItemBreadcrumb state={subItemState} />
|
||||
</NavigationDrawerAnimatedCollapseWrapper>
|
||||
)}
|
||||
<StyledItemElementsContainer>
|
||||
{Icon && (
|
||||
<Icon
|
||||
style={{ minWidth: theme.icon.size.md }}
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.md}
|
||||
color={
|
||||
showBreadcrumb && !isSettingsPage && !isNavigationDrawerExpanded
|
||||
? theme.font.color.light
|
||||
: 'currentColor'
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<NavigationDrawerAnimatedCollapseWrapper>
|
||||
<StyledItemLabel>{label}</StyledItemLabel>
|
||||
</NavigationDrawerAnimatedCollapseWrapper>
|
||||
|
||||
{soon && (
|
||||
<NavigationDrawerAnimatedCollapseWrapper>
|
||||
<Pill label="Soon" />
|
||||
</NavigationDrawerAnimatedCollapseWrapper>
|
||||
)}
|
||||
|
||||
{!!count && (
|
||||
<NavigationDrawerAnimatedCollapseWrapper>
|
||||
<StyledItemCount>{count}</StyledItemCount>
|
||||
</NavigationDrawerAnimatedCollapseWrapper>
|
||||
)}
|
||||
|
||||
{keyboard && (
|
||||
<NavigationDrawerAnimatedCollapseWrapper>
|
||||
<StyledKeyBoardShortcut className="keyboard-shortcuts">
|
||||
{keyboard}
|
||||
</StyledKeyBoardShortcut>
|
||||
</NavigationDrawerAnimatedCollapseWrapper>
|
||||
)}
|
||||
</StyledItemElementsContainer>
|
||||
</StyledItem>
|
||||
</StyledNavigationDrawerItemContainer>
|
||||
);
|
||||
|
||||
@ -6,9 +6,10 @@ export type NavigationDrawerItemBreadcrumbProps = {
|
||||
};
|
||||
|
||||
const StyledNavigationDrawerItemBreadcrumbContainer = styled.div`
|
||||
margin-left: 7.5px;
|
||||
|
||||
height: 28px;
|
||||
|
||||
margin-left: 7.5px;
|
||||
margin-right: ${({ theme }) => theme.spacing(2)};
|
||||
width: 9px;
|
||||
`;
|
||||
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { ReactNode } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
|
||||
import { AnimationControls, motion, TargetAndTransition } from 'framer-motion';
|
||||
import { useTheme } from '@emotion/react';
|
||||
|
||||
const StyledAnimationGroupContainer = styled(motion.div)``;
|
||||
|
||||
type NavigationDrawerItemsCollapsedContainerProps = {
|
||||
isGroup?: boolean;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const NavigationDrawerItemsCollapsedContainer = ({
|
||||
isGroup = false,
|
||||
children,
|
||||
}: NavigationDrawerItemsCollapsedContainerProps) => {
|
||||
const theme = useTheme();
|
||||
const isSettingsPage = useIsSettingsPage();
|
||||
const isNavigationDrawerExpanded = useRecoilValue(
|
||||
isNavigationDrawerExpandedState,
|
||||
);
|
||||
const isExpanded = isNavigationDrawerExpanded || isSettingsPage;
|
||||
let animate: AnimationControls | TargetAndTransition = {
|
||||
width: 'auto',
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
};
|
||||
if (!isExpanded) {
|
||||
animate = { width: 24 };
|
||||
if (isGroup) {
|
||||
animate = {
|
||||
width: 24,
|
||||
backgroundColor: theme.background.transparent.lighter,
|
||||
border: `1px solid ${theme.background.transparent.lighter}`,
|
||||
borderRadius: theme.border.radius.sm,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledAnimationGroupContainer
|
||||
initial={false}
|
||||
animate={animate}
|
||||
transition={{ duration: theme.animation.duration.normal }}
|
||||
>
|
||||
{children}
|
||||
</StyledAnimationGroupContainer>
|
||||
);
|
||||
};
|
||||
@ -1,8 +1,10 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
|
||||
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
|
||||
import { NavigationDrawerSectionTitleSkeletonLoader } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitleSkeletonLoader';
|
||||
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
@ -37,9 +39,22 @@ export const NavigationDrawerSectionTitle = ({
|
||||
}: NavigationDrawerSectionTitleProps) => {
|
||||
const currentUser = useRecoilValue(currentUserState);
|
||||
const loading = useIsPrefetchLoading();
|
||||
const isNavigationDrawerExpanded = useRecoilValue(
|
||||
isNavigationDrawerExpandedState,
|
||||
);
|
||||
|
||||
const isSettingsPage = useIsSettingsPage();
|
||||
|
||||
if (loading && isDefined(currentUser)) {
|
||||
return <NavigationDrawerSectionTitleSkeletonLoader />;
|
||||
}
|
||||
return <StyledTitle onClick={onClick}>{label}</StyledTitle>;
|
||||
return (
|
||||
<StyledTitle
|
||||
onClick={
|
||||
isNavigationDrawerExpanded || isSettingsPage ? onClick : undefined
|
||||
}
|
||||
>
|
||||
{label}
|
||||
</StyledTitle>
|
||||
);
|
||||
};
|
||||
|
||||
@ -53,6 +53,11 @@ export const Default: Story = {
|
||||
Icon={IconBell}
|
||||
soon={true}
|
||||
/>
|
||||
<NavigationDrawerItem
|
||||
label="Search"
|
||||
Icon={IconSearch}
|
||||
keyboard={['⌘', 'K']}
|
||||
/>
|
||||
<NavigationDrawerItem
|
||||
label="Settings"
|
||||
to="/settings/profile"
|
||||
@ -84,9 +89,8 @@ export const Default: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const Submenu: Story = {
|
||||
export const Settings: Story = {
|
||||
args: {
|
||||
isSubMenu: true,
|
||||
title: 'Settings',
|
||||
children: (
|
||||
<>
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
export const DESKTOP_NAV_DRAWER_WIDTHS = {
|
||||
menu: 220,
|
||||
};
|
||||
@ -0,0 +1,12 @@
|
||||
export const NAV_DRAWER_WIDTHS = {
|
||||
menu: {
|
||||
mobile: {
|
||||
collapsed: 0,
|
||||
expanded: '100%',
|
||||
},
|
||||
desktop: {
|
||||
collapsed: 40,
|
||||
expanded: 220,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -3,7 +3,7 @@ import { MOBILE_VIEWPORT } from 'twenty-ui';
|
||||
|
||||
const isMobile = window.innerWidth <= MOBILE_VIEWPORT;
|
||||
|
||||
export const isNavigationDrawerOpenState = atom({
|
||||
key: 'isNavigationDrawerOpen',
|
||||
export const isNavigationDrawerExpandedState = atom({
|
||||
key: 'isNavigationDrawerExpanded',
|
||||
default: !isMobile,
|
||||
});
|
||||
@ -0,0 +1,9 @@
|
||||
import { atom } from 'recoil';
|
||||
import { MOBILE_VIEWPORT } from 'twenty-ui';
|
||||
|
||||
const isMobile = window.innerWidth <= MOBILE_VIEWPORT;
|
||||
|
||||
export const navigationDrawerExpandedMemorizedState = atom({
|
||||
key: 'navigationDrawerExpandedMemorized',
|
||||
default: !isMobile,
|
||||
});
|
||||
Reference in New Issue
Block a user