mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-24 09:48:28 +02:00
Separate floating sidebar hidden state
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|||||||
@@ -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 }}
|
||||||
|
|||||||
13
src-web/hooks/useFloatingSidebarHidden.ts
Normal file
13
src-web/hooks/useFloatingSidebarHidden.ts
Normal 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;
|
||||||
|
}
|
||||||
8
src-web/hooks/useShouldFloatSidebar.ts
Normal file
8
src-web/hooks/useShouldFloatSidebar.ts
Normal 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;
|
||||||
|
}
|
||||||
@@ -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]);
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user