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:
Félix Malfait
2025-12-18 09:09:21 +01:00
committed by GitHub
parent 6682d4eb02
commit 4fe8e3d3b6
35 changed files with 671 additions and 206 deletions
@@ -1,5 +1,5 @@
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { COMMAND_MENU_SIDE_PANEL_WIDTH } from '@/command-menu/constants/CommandMenuSidePanelWidth';
import { commandMenuWidthState } from '@/command-menu/states/commandMenuWidthState';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { useListenToSidePanelClosing } from '@/ui/layout/right-drawer/hooks/useListenToSidePanelClosing';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
@@ -222,7 +222,7 @@ export const WorkflowDiagramCanvasBase = ({
const containerRef = useRef<HTMLDivElement>(null);
const setFlowViewport = useRecoilCallback(
() =>
({ snapshot }) =>
({
workflowDiagramFlowInitialized,
isCommandMenuOpened,
@@ -260,12 +260,15 @@ export const WorkflowDiagramCanvasBase = ({
let adjustedContainerWidth = baseContainerWidth;
const commandMenuWidth = getSnapshotValue(
snapshot,
commandMenuWidthState,
);
if (!isInRightDrawer && isCommandMenuOpened) {
adjustedContainerWidth =
baseContainerWidth - COMMAND_MENU_SIDE_PANEL_WIDTH;
adjustedContainerWidth = baseContainerWidth - commandMenuWidth;
} else if (!isInRightDrawer && hasViewportBeenMoved) {
adjustedContainerWidth =
baseContainerWidth + COMMAND_MENU_SIDE_PANEL_WIDTH;
adjustedContainerWidth = baseContainerWidth + commandMenuWidth;
}
const flowBounds = reactflow.getNodesBounds(nodes);