Separate floating sidebar hidden state

This commit is contained in:
Gregory Schier
2024-03-22 10:43:10 -07:00
parent e292235792
commit 00b1f90074
6 changed files with 57 additions and 42 deletions

View File

@@ -56,7 +56,7 @@ interface TreeNode {
} }
export function Sidebar({ className }: Props) { export function Sidebar({ className }: Props) {
const { hidden, show, hide } = useSidebarHidden(); const [hidden, setHidden] = useSidebarHidden();
const sidebarRef = useRef<HTMLLIElement>(null); const sidebarRef = useRef<HTMLLIElement>(null);
const activeRequest = useActiveRequest(); const activeRequest = useActiveRequest();
const activeEnvironmentId = useActiveEnvironmentId(); const activeEnvironmentId = useActiveEnvironmentId();
@@ -241,16 +241,15 @@ export function Sidebar({ className }: Props) {
useKeyPressEvent('Delete', handleDeleteKey); useKeyPressEvent('Delete', handleDeleteKey);
useHotKey('sidebar.focus', async () => { useHotKey('sidebar.focus', async () => {
console.log('sidebar.focus', { hidden, hasFocus });
// Hide the sidebar if it's already focused // Hide the sidebar if it's already focused
if (!hidden && hasFocus) { if (!hidden && hasFocus) {
await hide(); await setHidden(true);
return; return;
} }
// Show the sidebar if it's hidden // Show the sidebar if it's hidden
if (hidden) { if (hidden) {
await show(); await setHidden(false);
} }
// Select 0 index on focus if none selected // Select 0 index on focus if none selected

View File

@@ -1,12 +1,22 @@
import { memo } from 'react'; import { useMemo } from 'react';
import { useFloatingSidebarHidden } from '../hooks/useFloatingSidebarHidden';
import { useShouldFloatSidebar } from '../hooks/useShouldFloatSidebar';
import { useSidebarHidden } from '../hooks/useSidebarHidden'; import { useSidebarHidden } from '../hooks/useSidebarHidden';
import { trackEvent } from '../lib/analytics'; import { trackEvent } from '../lib/analytics';
import { IconButton } from './core/IconButton'; import { IconButton } from './core/IconButton';
import { HStack } from './core/Stacks'; import { HStack } from './core/Stacks';
import { CreateDropdown } from './CreateDropdown'; import { CreateDropdown } from './CreateDropdown';
export const SidebarActions = memo(function SidebarActions() { export function SidebarActions() {
const { hidden, show, hide } = useSidebarHidden(); const floating = useShouldFloatSidebar();
const [normalHidden, setNormalHidden] = useSidebarHidden();
const [floatingHidden, setFloatingHidden] = useFloatingSidebarHidden();
const hidden = floating ? floatingHidden : normalHidden;
const setHidden = useMemo(
() => (floating ? setFloatingHidden : setNormalHidden),
[floating, setFloatingHidden, setNormalHidden],
);
return ( return (
<HStack className="h-full" alignItems="center"> <HStack className="h-full" alignItems="center">
@@ -14,10 +24,9 @@ export const SidebarActions = memo(function SidebarActions() {
onClick={async () => { onClick={async () => {
trackEvent('sidebar', 'toggle'); trackEvent('sidebar', 'toggle');
// NOTE: We're not using `toggle` because it may be out of sync // NOTE: We're not using the (h) => !h pattern here because the data
// from changes in other windows // might be different if another window changed it (out of sync)
if (hidden) await show(); await setHidden(!hidden);
else await hide();
}} }}
className="pointer-events-auto" className="pointer-events-auto"
size="sm" size="sm"
@@ -35,4 +44,4 @@ export const SidebarActions = memo(function SidebarActions() {
</CreateDropdown> </CreateDropdown>
</HStack> </HStack>
); );
}); }

View File

@@ -6,14 +6,16 @@ import type {
MouseEvent as ReactMouseEvent, MouseEvent as ReactMouseEvent,
ReactNode, ReactNode,
} from 'react'; } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useMemo, useRef, useState } from 'react';
import { useWindowSize } from 'react-use'; import { useWindowSize } from 'react-use';
import { useActiveRequest } from '../hooks/useActiveRequest'; import { useActiveRequest } from '../hooks/useActiveRequest';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace'; import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { useActiveWorkspaceId } from '../hooks/useActiveWorkspaceId'; import { useActiveWorkspaceId } from '../hooks/useActiveWorkspaceId';
import { useFloatingSidebarHidden } from '../hooks/useFloatingSidebarHidden';
import { useImportData } from '../hooks/useImportData'; import { useImportData } from '../hooks/useImportData';
import { useIsFullscreen } from '../hooks/useIsFullscreen'; import { useIsFullscreen } from '../hooks/useIsFullscreen';
import { useOsInfo } from '../hooks/useOsInfo'; import { useOsInfo } from '../hooks/useOsInfo';
import { useShouldFloatSidebar } from '../hooks/useShouldFloatSidebar';
import { useSidebarHidden } from '../hooks/useSidebarHidden'; import { useSidebarHidden } from '../hooks/useSidebarHidden';
import { useSidebarWidth } from '../hooks/useSidebarWidth'; import { useSidebarWidth } from '../hooks/useSidebarWidth';
import { useWorkspaces } from '../hooks/useWorkspaces'; import { useWorkspaces } from '../hooks/useWorkspaces';
@@ -37,34 +39,22 @@ const head = { gridArea: 'head' };
const body = { gridArea: 'body' }; const body = { gridArea: 'body' };
const drag = { gridArea: 'drag' }; const drag = { gridArea: 'drag' };
const WINDOW_FLOATING_SIDEBAR_WIDTH = 600;
export default function Workspace() { export default function Workspace() {
const workspaces = useWorkspaces(); const workspaces = useWorkspaces();
const activeWorkspace = useActiveWorkspace(); const activeWorkspace = useActiveWorkspace();
const activeWorkspaceId = useActiveWorkspaceId(); const activeWorkspaceId = useActiveWorkspaceId();
const { setWidth, width, resetWidth } = useSidebarWidth(); const { setWidth, width, resetWidth } = useSidebarWidth();
const { hide, show, hidden } = useSidebarHidden(); const [sidebarHidden, setSidebarHidden] = useSidebarHidden();
const [floatingSidebarHidden, setFloatingSidebarHidden] = useFloatingSidebarHidden();
const activeRequest = useActiveRequest(); const activeRequest = useActiveRequest();
const windowSize = useWindowSize(); const windowSize = useWindowSize();
const importData = useImportData(); const importData = useImportData();
const [floating, setFloating] = useState<boolean>(false); const floating = useShouldFloatSidebar();
const [isResizing, setIsResizing] = useState<boolean>(false); const [isResizing, setIsResizing] = useState<boolean>(false);
const moveState = useRef<{ move: (e: MouseEvent) => void; up: (e: MouseEvent) => void } | null>( const moveState = useRef<{ move: (e: MouseEvent) => void; up: (e: MouseEvent) => void } | null>(
null, null,
); );
// float/un-float sidebar on window resize
useEffect(() => {
const shouldHide = windowSize.width <= WINDOW_FLOATING_SIDEBAR_WIDTH;
if (shouldHide && !floating) {
setFloating(true);
hide().catch(console.error);
} else if (!shouldHide && floating) {
setFloating(false);
}
}, [floating, hide, windowSize.width]);
const unsub = () => { const unsub = () => {
if (moveState.current !== null) { if (moveState.current !== null) {
document.documentElement.removeEventListener('mousemove', moveState.current.move); document.documentElement.removeEventListener('mousemove', moveState.current.move);
@@ -84,10 +74,10 @@ export default function Workspace() {
e.preventDefault(); // Prevent text selection and things e.preventDefault(); // Prevent text selection and things
const newWidth = startWidth + (e.clientX - mouseStartX); const newWidth = startWidth + (e.clientX - mouseStartX);
if (newWidth < 50) { if (newWidth < 50) {
await hide(); await setSidebarHidden(true);
resetWidth(); resetWidth();
} else { } else {
await show(); await setSidebarHidden(false);
setWidth(newWidth); setWidth(newWidth);
} }
}, },
@@ -101,10 +91,10 @@ export default function Workspace() {
document.documentElement.addEventListener('mouseup', moveState.current.up); document.documentElement.addEventListener('mouseup', moveState.current.up);
setIsResizing(true); setIsResizing(true);
}, },
[setWidth, resetWidth, width, hide, show], [width, setSidebarHidden, resetWidth, setWidth],
); );
const sideWidth = hidden ? 0 : width; const sideWidth = sidebarHidden ? 0 : width;
const styles = useMemo<CSSProperties>( const styles = useMemo<CSSProperties>(
() => ({ () => ({
gridTemplate: floating gridTemplate: floating
@@ -144,7 +134,11 @@ export default function Workspace() {
)} )}
> >
{floating ? ( {floating ? (
<Overlay open={!hidden} portalName="sidebar" onClose={hide}> <Overlay
open={!floatingSidebarHidden}
portalName="sidebar"
onClose={() => setFloatingSidebarHidden(true)}
>
<motion.div <motion.div
initial={{ opacity: 0, x: -20 }} initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}

View File

@@ -0,0 +1,13 @@
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useKeyValue } from './useKeyValue';
export function useFloatingSidebarHidden() {
const activeWorkspaceId = useActiveWorkspaceId();
const { set, value } = useKeyValue<boolean>({
namespace: 'no_sync',
key: ['floating_sidebar_hidden', activeWorkspaceId ?? 'n/a'],
fallback: false,
});
return [value, set] as const;
}

View File

@@ -0,0 +1,8 @@
import { useWindowSize } from 'react-use';
const WINDOW_FLOATING_SIDEBAR_WIDTH = 600;
export function useShouldFloatSidebar() {
const windowSize = useWindowSize();
return windowSize.width <= WINDOW_FLOATING_SIDEBAR_WIDTH;
}

View File

@@ -1,4 +1,3 @@
import { useMemo } from 'react';
import { useActiveWorkspaceId } from './useActiveWorkspaceId'; import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useKeyValue } from './useKeyValue'; import { useKeyValue } from './useKeyValue';
@@ -10,12 +9,5 @@ export function useSidebarHidden() {
fallback: false, fallback: false,
}); });
return useMemo(() => { return [value, set] as const;
return {
show: () => set(false),
hide: () => set(true),
toggle: () => set((h) => !h),
hidden: value,
};
}, [set, value]);
} }