feat: add resizable navigation drawer and command menu panels (#16612)
## Summary Adds Notion-style resizable panels for the navigation drawer (left sidebar) and command menu (right panel). ## Behavior - **Hover** at panel edge → resize cursor appears - **Click** → collapse/close the panel - **Drag** → resize the panel (5px movement threshold to distinguish from click) ## Constraints | Panel | Min | Max | Default | Collapse Threshold | |-------|-----|-----|---------|-------------------| | Navigation Drawer | 180px | 350px | 220px | 150px | | Command Menu | 320px | 600px | 400px | 250px | ## Performance Optimizations - **CSS variables** for smooth 60fps resize (no React re-renders during drag) - **Table resize observer disabled** during panel resize to prevent expensive recalculations - **React.memo wrapper** on page body to prevent unnecessary re-renders ## Architecture - `useResizablePanel` hook following the same pattern as `useResizeTableHeader` - `ResizablePanelEdge` - resize handle positioned at panel edge - `ResizablePanelGap` - resize handle in the gap between panels - `cssVariableEffect` - Recoil effect to sync CSS variables with state ## Refactoring - Split `recoil-effects.ts` into separate files in `utils/recoil/` (one export per file) - Persist panel widths to localStorage via existing `localStorageEffect`
This commit is contained in:
@@ -15,7 +15,7 @@ import { SignInAppNavigationDrawerMock } from '@/sign-in-background-mock/compone
|
||||
import { SignInBackgroundMockPage } from '@/sign-in-background-mock/components/SignInBackgroundMockPage';
|
||||
import { useShowFullscreen } from '@/ui/layout/fullscreen/hooks/useShowFullscreen';
|
||||
import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal';
|
||||
import { NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/NavDrawerWidths';
|
||||
import { NAVIGATION_DRAWER_CONSTRAINTS } from '@/ui/layout/resizable-panel/constants/NavigationDrawerConstraints';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { Global, css, useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
@@ -87,7 +87,7 @@ export const DefaultLayout = () => {
|
||||
isSettingsPage && !isMobile && !useShowFullScreen
|
||||
? (windowsWidth -
|
||||
(OBJECT_SETTINGS_WIDTH +
|
||||
NAV_DRAWER_WIDTHS.menu.desktop.expanded +
|
||||
NAVIGATION_DRAWER_CONSTRAINTS.default +
|
||||
76)) /
|
||||
2
|
||||
: 0,
|
||||
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { RESIZE_EDGE_WIDTH_PX } from '../constants/ResizeEdgeWidthPx';
|
||||
import { useResizablePanel } from '../hooks/useResizablePanel';
|
||||
import { type ResizablePanelConstraints } from '../types/ResizablePanelConstraints';
|
||||
import { type ResizablePanelSide } from '../types/ResizablePanelSide';
|
||||
|
||||
type StyledEdgeProps = {
|
||||
isActive: boolean;
|
||||
isHovered: boolean;
|
||||
side: ResizablePanelSide;
|
||||
};
|
||||
|
||||
const StyledEdge = styled.div<StyledEdgeProps>`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
${({ side }) =>
|
||||
side === 'right' ? 'right' : 'left'}: -${RESIZE_EDGE_WIDTH_PX / 2}px;
|
||||
width: ${RESIZE_EDGE_WIDTH_PX}px;
|
||||
cursor: col-resize;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const StyledHandle = styled.div<{ isActive: boolean; isHovered: boolean }>`
|
||||
width: 4px;
|
||||
height: 48px;
|
||||
border-radius: ${({ theme }) => theme.border.radius.pill};
|
||||
background-color: ${({ theme, isActive, isHovered }) =>
|
||||
isActive
|
||||
? theme.color.blue
|
||||
: isHovered
|
||||
? theme.font.color.tertiary
|
||||
: theme.background.quaternary};
|
||||
transition:
|
||||
background-color ${({ theme }) => theme.animation.duration.fast}s,
|
||||
transform ${({ theme }) => theme.animation.duration.fast}s;
|
||||
transform: ${({ isHovered, isActive }) =>
|
||||
isHovered || isActive ? 'scaleY(1.2)' : 'scaleY(1)'};
|
||||
`;
|
||||
|
||||
type ResizablePanelEdgeProps = {
|
||||
side: ResizablePanelSide;
|
||||
constraints: ResizablePanelConstraints;
|
||||
currentWidth: number;
|
||||
onWidthChange: (width: number) => void;
|
||||
onCollapse: () => void;
|
||||
showHandle?: boolean;
|
||||
cssVariableName?: string;
|
||||
onResizeStart?: () => void;
|
||||
};
|
||||
|
||||
export const ResizablePanelEdge = ({
|
||||
side,
|
||||
constraints,
|
||||
currentWidth,
|
||||
onWidthChange,
|
||||
onCollapse,
|
||||
showHandle = true,
|
||||
cssVariableName,
|
||||
onResizeStart,
|
||||
}: ResizablePanelEdgeProps) => {
|
||||
const {
|
||||
isHovered,
|
||||
isResizing,
|
||||
handleMouseDown,
|
||||
handleMouseEnter,
|
||||
handleMouseLeave,
|
||||
} = useResizablePanel({
|
||||
side,
|
||||
constraints,
|
||||
currentWidth,
|
||||
onWidthChange,
|
||||
onCollapse,
|
||||
cssVariableName,
|
||||
onResizeStart,
|
||||
});
|
||||
|
||||
return (
|
||||
<StyledEdge
|
||||
side={side}
|
||||
isActive={isResizing}
|
||||
isHovered={isHovered}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{showHandle && (
|
||||
<StyledHandle isActive={isResizing} isHovered={isHovered} />
|
||||
)}
|
||||
</StyledEdge>
|
||||
);
|
||||
};
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useResizablePanel } from '../hooks/useResizablePanel';
|
||||
import { type ResizablePanelConstraints } from '../types/ResizablePanelConstraints';
|
||||
import { type ResizablePanelSide } from '../types/ResizablePanelSide';
|
||||
|
||||
const StyledGap = styled.div<{ gapWidth: number }>`
|
||||
cursor: col-resize;
|
||||
flex-shrink: 0;
|
||||
height: 100%;
|
||||
width: ${({ gapWidth }) => gapWidth}px;
|
||||
transition: width 0.15s ease;
|
||||
`;
|
||||
|
||||
type ResizablePanelGapProps = {
|
||||
side: ResizablePanelSide;
|
||||
constraints: ResizablePanelConstraints;
|
||||
currentWidth: number;
|
||||
onWidthChange: (width: number) => void;
|
||||
onCollapse: () => void;
|
||||
gapWidth: number;
|
||||
cssVariableName?: string;
|
||||
onResizeStart?: () => void;
|
||||
};
|
||||
|
||||
export const ResizablePanelGap = ({
|
||||
side,
|
||||
constraints,
|
||||
currentWidth,
|
||||
onWidthChange,
|
||||
onCollapse,
|
||||
gapWidth,
|
||||
cssVariableName,
|
||||
onResizeStart,
|
||||
}: ResizablePanelGapProps) => {
|
||||
const { handleMouseDown, handleMouseEnter, handleMouseLeave } =
|
||||
useResizablePanel({
|
||||
side,
|
||||
constraints,
|
||||
currentWidth,
|
||||
onWidthChange,
|
||||
onCollapse,
|
||||
cssVariableName,
|
||||
onResizeStart,
|
||||
});
|
||||
|
||||
return (
|
||||
<StyledGap
|
||||
gapWidth={gapWidth}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { type ResizablePanelConstraints } from '../types/ResizablePanelConstraints';
|
||||
|
||||
export const COMMAND_MENU_CONSTRAINTS: ResizablePanelConstraints = {
|
||||
min: 320,
|
||||
max: 600,
|
||||
default: 400,
|
||||
};
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const NAVIGATION_DRAWER_COLLAPSED_WIDTH = 40;
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { type ResizablePanelConstraints } from '../types/ResizablePanelConstraints';
|
||||
|
||||
export const NAVIGATION_DRAWER_CONSTRAINTS: ResizablePanelConstraints = {
|
||||
min: 180,
|
||||
max: 350,
|
||||
default: 220,
|
||||
};
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const RESIZE_DRAG_THRESHOLD_PX = 5;
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const RESIZE_EDGE_WIDTH_PX = 8;
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer';
|
||||
import { type PointerEventListener } from '@/ui/utilities/pointer-event/types/PointerEventListener';
|
||||
|
||||
import { RESIZE_DRAG_THRESHOLD_PX } from '../constants/ResizeDragThresholdPx';
|
||||
import { type ResizablePanelConstraints } from '../types/ResizablePanelConstraints';
|
||||
import { type ResizablePanelSide } from '../types/ResizablePanelSide';
|
||||
|
||||
type UseResizablePanelProps = {
|
||||
side: ResizablePanelSide;
|
||||
constraints: ResizablePanelConstraints;
|
||||
currentWidth: number;
|
||||
onWidthChange: (width: number) => void;
|
||||
onCollapse: () => void;
|
||||
cssVariableName?: string;
|
||||
onResizeStart?: () => void;
|
||||
};
|
||||
|
||||
const clampWidth = (width: number, min: number, max: number): number =>
|
||||
Math.min(max, Math.max(min, width));
|
||||
|
||||
export const useResizablePanel = ({
|
||||
side,
|
||||
constraints,
|
||||
currentWidth,
|
||||
onWidthChange,
|
||||
onCollapse,
|
||||
cssVariableName,
|
||||
onResizeStart,
|
||||
}: UseResizablePanelProps) => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const [isResizing, setIsResizing] = useState(false);
|
||||
const [startX, setStartX] = useState<number | null>(null);
|
||||
const [startWidth, setStartWidth] = useState<number>(0);
|
||||
const [hasDragged, setHasDragged] = useState(false);
|
||||
|
||||
const handleResizeMove = useCallback<PointerEventListener>(
|
||||
({ x }) => {
|
||||
if (startX === null) return;
|
||||
|
||||
const deltaX = x - startX;
|
||||
|
||||
if (!hasDragged && Math.abs(deltaX) > RESIZE_DRAG_THRESHOLD_PX) {
|
||||
setHasDragged(true);
|
||||
onResizeStart?.();
|
||||
}
|
||||
|
||||
if (Math.abs(deltaX) > RESIZE_DRAG_THRESHOLD_PX) {
|
||||
const widthDelta = side === 'right' ? deltaX : -deltaX;
|
||||
const clampedWidth = clampWidth(
|
||||
startWidth + widthDelta,
|
||||
constraints.min,
|
||||
constraints.max,
|
||||
);
|
||||
|
||||
if (cssVariableName !== undefined) {
|
||||
document.documentElement.style.setProperty(
|
||||
cssVariableName,
|
||||
`${clampedWidth}px`,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
startX,
|
||||
startWidth,
|
||||
hasDragged,
|
||||
side,
|
||||
constraints.min,
|
||||
constraints.max,
|
||||
cssVariableName,
|
||||
onResizeStart,
|
||||
],
|
||||
);
|
||||
|
||||
const handleResizeEnd = useCallback<PointerEventListener>(
|
||||
({ x }) => {
|
||||
if (startX === null) {
|
||||
setIsResizing(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const deltaX = x - startX;
|
||||
|
||||
if (!hasDragged) {
|
||||
onCollapse();
|
||||
} else {
|
||||
const widthDelta = side === 'right' ? deltaX : -deltaX;
|
||||
const finalWidth = clampWidth(
|
||||
startWidth + widthDelta,
|
||||
constraints.min,
|
||||
constraints.max,
|
||||
);
|
||||
onWidthChange(finalWidth);
|
||||
}
|
||||
|
||||
setStartX(null);
|
||||
setIsResizing(false);
|
||||
},
|
||||
[
|
||||
startX,
|
||||
startWidth,
|
||||
hasDragged,
|
||||
side,
|
||||
constraints.min,
|
||||
constraints.max,
|
||||
onCollapse,
|
||||
onWidthChange,
|
||||
],
|
||||
);
|
||||
|
||||
useTrackPointer({
|
||||
shouldTrackPointer: isResizing,
|
||||
onMouseMove: handleResizeMove,
|
||||
onMouseUp: handleResizeEnd,
|
||||
});
|
||||
|
||||
const handleMouseDown = useCallback(
|
||||
(event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
setStartX(event.clientX);
|
||||
setStartWidth(currentWidth);
|
||||
setHasDragged(false);
|
||||
setIsResizing(true);
|
||||
},
|
||||
[currentWidth],
|
||||
);
|
||||
|
||||
const handleMouseEnter = useCallback(() => {
|
||||
setIsHovered(true);
|
||||
}, []);
|
||||
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
setIsHovered(false);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
isHovered,
|
||||
isResizing,
|
||||
handleMouseDown,
|
||||
handleMouseEnter,
|
||||
handleMouseLeave,
|
||||
};
|
||||
};
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
export type ResizablePanelConstraints = {
|
||||
min: number;
|
||||
max: number;
|
||||
default: number;
|
||||
};
|
||||
+1
@@ -0,0 +1 @@
|
||||
export type ResizablePanelSide = 'left' | 'right';
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import {
|
||||
NAVIGATION_DRAWER_WIDTH_VAR,
|
||||
navigationDrawerWidthState,
|
||||
} from '../states/navigationDrawerWidthState';
|
||||
|
||||
export const NavigationDrawerWidthEffect = () => {
|
||||
const navigationDrawerWidth = useRecoilValue(navigationDrawerWidthState);
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.style.setProperty(
|
||||
NAVIGATION_DRAWER_WIDTH_VAR,
|
||||
`${navigationDrawerWidth}px`,
|
||||
);
|
||||
}, [navigationDrawerWidth]);
|
||||
|
||||
return null;
|
||||
};
|
||||
+88
-42
@@ -1,15 +1,20 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { motion } from 'framer-motion';
|
||||
import { type ReactNode, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/NavDrawerWidths';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { type ReactNode, useCallback, useState } from 'react';
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useIsSettingsDrawer } from '@/navigation/hooks/useIsSettingsDrawer';
|
||||
import { tableWidthResizeIsActiveState } from '@/object-record/record-table/states/tableWidthResizeIsActivedState';
|
||||
import { ResizablePanelEdge } from '@/ui/layout/resizable-panel/components/ResizablePanelEdge';
|
||||
import { NAVIGATION_DRAWER_COLLAPSED_WIDTH } from '@/ui/layout/resizable-panel/constants/NavigationDrawerCollapsedWidth';
|
||||
import { NAVIGATION_DRAWER_CONSTRAINTS } from '@/ui/layout/resizable-panel/constants/NavigationDrawerConstraints';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
|
||||
import { isNavigationDrawerExpandedState } from '../../states/isNavigationDrawerExpanded';
|
||||
import {
|
||||
NAVIGATION_DRAWER_WIDTH_VAR,
|
||||
navigationDrawerWidthState,
|
||||
} from '../../states/navigationDrawerWidthState';
|
||||
import { NavigationDrawerWidthEffect } from '../../components/NavigationDrawerWidthEffect';
|
||||
import { NavigationDrawerBackButton } from './NavigationDrawerBackButton';
|
||||
import { NavigationDrawerHeader } from './NavigationDrawerHeader';
|
||||
|
||||
@@ -19,9 +24,23 @@ export type NavigationDrawerProps = {
|
||||
title: string;
|
||||
};
|
||||
|
||||
const StyledAnimatedContainer = styled(motion.div)`
|
||||
const StyledAnimatedContainer = styled.div<{
|
||||
isExpanded: boolean;
|
||||
isResizing: boolean;
|
||||
}>`
|
||||
max-height: 100vh;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: ${({ isExpanded }) =>
|
||||
isExpanded
|
||||
? `var(${NAVIGATION_DRAWER_WIDTH_VAR})`
|
||||
: `${NAVIGATION_DRAWER_COLLAPSED_WIDTH}px`};
|
||||
transition: ${({ isResizing, theme }) =>
|
||||
isResizing ? 'none' : `width ${theme.animation.duration.normal}s`};
|
||||
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
width: ${({ isExpanded }) => (isExpanded ? '100%' : '0')};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledContainer = styled.div<{
|
||||
@@ -31,8 +50,7 @@ const StyledContainer = styled.div<{
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: ${({ isSettings }) =>
|
||||
isSettings ? '100%' : NAV_DRAWER_WIDTHS.menu.desktop.expanded + 'px'};
|
||||
width: var(${NAVIGATION_DRAWER_WIDTH_VAR});
|
||||
gap: ${({ theme }) => theme.spacing(3)};
|
||||
height: 100%;
|
||||
padding: ${({ theme, isSettings, isMobile }) =>
|
||||
@@ -54,11 +72,17 @@ export const NavigationDrawer = ({
|
||||
title,
|
||||
}: NavigationDrawerProps) => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const [isResizing, setIsResizing] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
const isSettingsDrawer = useIsSettingsDrawer();
|
||||
const theme = useTheme();
|
||||
const isNavigationDrawerExpanded = useRecoilValue(
|
||||
isNavigationDrawerExpandedState,
|
||||
|
||||
const [isNavigationDrawerExpanded, setIsNavigationDrawerExpanded] =
|
||||
useRecoilState(isNavigationDrawerExpandedState);
|
||||
const [navigationDrawerWidth, setNavigationDrawerWidth] = useRecoilState(
|
||||
navigationDrawerWidthState,
|
||||
);
|
||||
const setTableWidthResizeIsActive = useSetRecoilState(
|
||||
tableWidthResizeIsActiveState,
|
||||
);
|
||||
|
||||
const handleHover = () => {
|
||||
@@ -69,40 +93,62 @@ export const NavigationDrawer = ({
|
||||
setIsHovered(false);
|
||||
};
|
||||
|
||||
const desktopWidth = isNavigationDrawerExpanded
|
||||
? NAV_DRAWER_WIDTHS.menu.desktop.expanded
|
||||
: NAV_DRAWER_WIDTHS.menu.desktop.collapsed;
|
||||
const handleCollapse = useCallback(() => {
|
||||
setIsNavigationDrawerExpanded(false);
|
||||
setIsResizing(false);
|
||||
setTableWidthResizeIsActive(true);
|
||||
}, [setIsNavigationDrawerExpanded, setTableWidthResizeIsActive]);
|
||||
|
||||
const mobileWidth = isNavigationDrawerExpanded
|
||||
? NAV_DRAWER_WIDTHS.menu.mobile.expanded
|
||||
: NAV_DRAWER_WIDTHS.menu.mobile.collapsed;
|
||||
const handleWidthChange = useCallback(
|
||||
(width: number) => {
|
||||
setNavigationDrawerWidth(width);
|
||||
setIsResizing(false);
|
||||
setTableWidthResizeIsActive(true);
|
||||
},
|
||||
[setNavigationDrawerWidth, setTableWidthResizeIsActive],
|
||||
);
|
||||
|
||||
const navigationDrawerAnimate = {
|
||||
width: isMobile ? mobileWidth : desktopWidth,
|
||||
opacity: isNavigationDrawerExpanded || !isSettingsDrawer ? 1 : 0,
|
||||
};
|
||||
const handleResizeStart = useCallback(() => {
|
||||
setIsResizing(true);
|
||||
setTableWidthResizeIsActive(false);
|
||||
}, [setTableWidthResizeIsActive]);
|
||||
|
||||
return (
|
||||
<StyledAnimatedContainer
|
||||
className={className}
|
||||
initial={false}
|
||||
animate={navigationDrawerAnimate}
|
||||
transition={{ duration: theme.animation.duration.normal }}
|
||||
>
|
||||
<StyledContainer
|
||||
isSettings={isSettingsDrawer}
|
||||
isMobile={isMobile}
|
||||
onMouseEnter={handleHover}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
<>
|
||||
<NavigationDrawerWidthEffect />
|
||||
<StyledAnimatedContainer
|
||||
className={className}
|
||||
isExpanded={isNavigationDrawerExpanded}
|
||||
isResizing={isResizing}
|
||||
>
|
||||
{isSettingsDrawer && title ? (
|
||||
!isMobile && <NavigationDrawerBackButton title={title} />
|
||||
) : (
|
||||
<NavigationDrawerHeader showCollapseButton={isHovered} />
|
||||
)}
|
||||
<StyledContainer
|
||||
isSettings={isSettingsDrawer}
|
||||
isMobile={isMobile}
|
||||
onMouseEnter={handleHover}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{isSettingsDrawer && title ? (
|
||||
!isMobile && <NavigationDrawerBackButton title={title} />
|
||||
) : (
|
||||
<NavigationDrawerHeader showCollapseButton={isHovered} />
|
||||
)}
|
||||
|
||||
{children}
|
||||
</StyledContainer>
|
||||
</StyledAnimatedContainer>
|
||||
{children}
|
||||
</StyledContainer>
|
||||
|
||||
{isNavigationDrawerExpanded && !isMobile && !isSettingsDrawer && (
|
||||
<ResizablePanelEdge
|
||||
side="right"
|
||||
constraints={NAVIGATION_DRAWER_CONSTRAINTS}
|
||||
currentWidth={navigationDrawerWidth}
|
||||
onWidthChange={handleWidthChange}
|
||||
onCollapse={handleCollapse}
|
||||
showHandle={false}
|
||||
cssVariableName={NAVIGATION_DRAWER_WIDTH_VAR}
|
||||
onResizeStart={handleResizeStart}
|
||||
/>
|
||||
)}
|
||||
</StyledAnimatedContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ import { t } from '@lingui/core/macro';
|
||||
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
|
||||
import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper';
|
||||
import { NavigationDrawerItemBreadcrumb } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemBreadcrumb';
|
||||
import { NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/NavDrawerWidths';
|
||||
import { NAVIGATION_DRAWER_COLLAPSED_WIDTH } from '@/ui/layout/resizable-panel/constants/NavigationDrawerCollapsedWidth';
|
||||
import { useNavigationDrawerTooltip } from '@/ui/navigation/navigation-drawer/hooks/useNavigationDrawerTooltip';
|
||||
import { type NavigationDrawerSubItemState } from '@/ui/navigation/navigation-drawer/types/NavigationDrawerSubItemState';
|
||||
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||
@@ -104,7 +104,7 @@ const StyledItem = styled('button', {
|
||||
|
||||
width: ${(props) =>
|
||||
!props.isNavigationDrawerExpanded
|
||||
? `calc(${NAV_DRAWER_WIDTHS.menu.desktop.collapsed}px - ${props.theme.spacing(6)})`
|
||||
? `calc(${NAVIGATION_DRAWER_COLLAPSED_WIDTH}px - ${props.theme.spacing(6)})`
|
||||
: `calc(100% - ${props.theme.spacing(1.5)})`};
|
||||
|
||||
${({ isDragging }) =>
|
||||
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
export const NAV_DRAWER_WIDTHS = {
|
||||
menu: {
|
||||
mobile: {
|
||||
collapsed: 0,
|
||||
expanded: '100%',
|
||||
},
|
||||
desktop: {
|
||||
collapsed: 40,
|
||||
expanded: 220,
|
||||
},
|
||||
},
|
||||
};
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { atom } from 'recoil';
|
||||
import { localStorageEffect } from '~/utils/recoil-effects';
|
||||
import { localStorageEffect } from '~/utils/recoil/localStorageEffect';
|
||||
|
||||
export const isAdvancedModeEnabledState = atom<boolean>({
|
||||
key: 'isAdvancedModeEnabledAtom',
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState';
|
||||
import { localStorageEffect } from '~/utils/recoil-effects';
|
||||
import { localStorageEffect } from '~/utils/recoil/localStorageEffect';
|
||||
|
||||
export const isNavigationSectionOpenFamilyState = createFamilyState<
|
||||
boolean,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { atom } from 'recoil';
|
||||
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
|
||||
import { localStorageEffect } from '~/utils/recoil-effects';
|
||||
import { localStorageEffect } from '~/utils/recoil/localStorageEffect';
|
||||
|
||||
const isMobile = window.innerWidth <= MOBILE_VIEWPORT;
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
import { NAVIGATION_DRAWER_CONSTRAINTS } from '@/ui/layout/resizable-panel/constants/NavigationDrawerConstraints';
|
||||
import { localStorageEffect } from '~/utils/recoil/localStorageEffect';
|
||||
|
||||
export const NAVIGATION_DRAWER_WIDTH_VAR = '--navigation-drawer-width';
|
||||
|
||||
export const navigationDrawerWidthState = atom<number>({
|
||||
key: 'navigationDrawerWidth',
|
||||
default: NAVIGATION_DRAWER_CONSTRAINTS.default,
|
||||
effects: [localStorageEffect()],
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
import { type ColorScheme } from '@/workspace-member/types/WorkspaceMember';
|
||||
import { localStorageEffect } from '~/utils/recoil-effects';
|
||||
import { localStorageEffect } from '~/utils/recoil/localStorageEffect';
|
||||
|
||||
export const persistedColorSchemeState = atom<ColorScheme>({
|
||||
key: 'persistedColorSchemeState',
|
||||
|
||||
Reference in New Issue
Block a user