Floating sidebar refactor

This commit is contained in:
Gregory Schier
2026-03-12 15:12:49 -07:00
parent cc504e0a1c
commit f7ff964fe5
9 changed files with 163 additions and 99 deletions

View File

@@ -1,28 +1,28 @@
import { useMemo } from 'react';
import { useFloatingSidebarHidden } from '../hooks/useFloatingSidebarHidden';
import { useShouldFloatSidebar } from '../hooks/useShouldFloatSidebar';
import { useSidebarHidden } from '../hooks/useSidebarHidden';
import { CreateDropdown } from './CreateDropdown';
import { IconButton } from './core/IconButton';
import { HStack } from './core/Stacks';
export function SidebarActions() {
const floating = useShouldFloatSidebar();
const [normalHidden, setNormalHidden] = useSidebarHidden();
interface Props {
floating?: boolean;
}
export function SidebarActions({ floating = false }: Props) {
const [sidebarHidden, setSidebarHidden] = useSidebarHidden();
const [floatingHidden, setFloatingHidden] = useFloatingSidebarHidden();
const hidden = floating ? floatingHidden : normalHidden;
const hidden = floating ? floatingHidden : sidebarHidden;
const setHidden = useMemo(
() => (floating ? setFloatingHidden : setNormalHidden),
[floating, setFloatingHidden, setNormalHidden],
() => (floating ? setFloatingHidden : setSidebarHidden),
[floating, setFloatingHidden, setSidebarHidden],
);
return (
<HStack className="h-full">
<IconButton
onClick={async () => {
// NOTE: We're not using the (h) => !h pattern here because the data
// might be different if another window changed it (out of sync)
await setHidden(!hidden);
}}
className="pointer-events-auto"

View File

@@ -1,10 +1,10 @@
import { type } from '@tauri-apps/plugin-os';
import { settingsAtom, workspacesAtom } from '@yaakapp-internal/models';
import { HeaderSize, Overlay, SidebarLayout } from '@yaakapp-internal/ui';
import { HeaderSize, SidebarLayout } from '@yaakapp-internal/ui';
import classNames from 'classnames';
import { useAtomValue } from 'jotai';
import * as m from 'motion/react-m';
import { useMemo } from 'react';
import { useMemo, useState } from 'react';
import {
useEnsureActiveCookieJar,
useSubscribeActiveCookieJarId,
@@ -24,7 +24,6 @@ import { useSubscribeRecentCookieJars } from '../hooks/useRecentCookieJars';
import { useSubscribeRecentEnvironments } from '../hooks/useRecentEnvironments';
import { useSubscribeRecentRequests } from '../hooks/useRecentRequests';
import { useSubscribeRecentWorkspaces } from '../hooks/useRecentWorkspaces';
import { useShouldFloatSidebar } from '../hooks/useShouldFloatSidebar';
import { useSidebarHidden } from '../hooks/useSidebarHidden';
import { useSidebarWidth } from '../hooks/useSidebarWidth';
import { useSyncWorkspaceRequestTitle } from '../hooks/useSyncWorkspaceRequestTitle';
@@ -55,11 +54,11 @@ export function Workspace() {
const workspaces = useAtomValue(workspacesAtom);
const settings = useAtomValue(settingsAtom);
const osType = type();
const [width, setWidth, resetWidth] = useSidebarWidth();
const [width, setWidth] = useSidebarWidth();
const [sidebarHidden, setSidebarHidden] = useSidebarHidden();
const [floatingSidebarHidden, setFloatingSidebarHidden] = useFloatingSidebarHidden();
const activeEnvironment = useAtomValue(activeEnvironmentAtom);
const floating = useShouldFloatSidebar();
const [floating, setFloating] = useState(false);
const environmentBgStyle = useMemo(() => {
if (activeEnvironment?.color == null) return undefined;
@@ -89,7 +88,7 @@ export function Workspace() {
className="absolute left-0 right-0 -bottom-[1px] h-[1px] opacity-20"
/>
</div>
<WorkspaceHeader className="pointer-events-none" />
<WorkspaceHeader className="pointer-events-none" floatingSidebar={floating} />
</HeaderSize>
);
@@ -99,7 +98,30 @@ export function Workspace() {
</ErrorBoundary>
);
const sidebarContent = (
const sidebarContent = floating ? (
<div
className={classNames(
'x-theme-sidebar',
'h-full bg-surface border-r border-border-subtle',
'grid grid-rows-[auto_1fr]',
)}
>
<HeaderSize
hideControls
size="lg"
className="border-transparent flex items-center"
osType={osType}
hideWindowControls={settings.hideWindowControls}
useNativeTitlebar={settings.useNativeTitlebar}
interfaceScale={settings.interfaceScale}
>
<SidebarActions floating />
</HeaderSize>
<ErrorBoundary name="Sidebar (Floating)">
<Sidebar />
</ErrorBoundary>
</div>
) : (
<div className="x-theme-sidebar overflow-hidden bg-surface h-full">
<ErrorBoundary name="Sidebar">
<Sidebar className="border-r border-border-subtle" />
@@ -110,52 +132,18 @@ export function Workspace() {
return (
<div className="grid w-full h-full grid-rows-[auto_1fr]">
{header}
{floating ? (
<>
<Overlay
open={!floatingSidebarHidden}
portalName="sidebar"
onClose={() => setFloatingSidebarHidden(true)}
zIndex={20}
>
<m.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
className={classNames(
'x-theme-sidebar',
'absolute top-0 left-0 bottom-0 bg-surface border-r border-border-subtle w-[20rem]',
'grid grid-rows-[auto_1fr]',
)}
>
<HeaderSize
hideControls
size="lg"
className="border-transparent flex items-center"
osType={osType}
hideWindowControls={settings.hideWindowControls}
useNativeTitlebar={settings.useNativeTitlebar}
interfaceScale={settings.interfaceScale}
>
<SidebarActions />
</HeaderSize>
<ErrorBoundary name="Sidebar (Floating)">
<Sidebar />
</ErrorBoundary>
</m.div>
</Overlay>
{workspaceBody}
</>
) : (
<SidebarLayout
width={width ?? 250}
onWidthChange={setWidth}
hidden={sidebarHidden ?? false}
onHiddenChange={(hidden) => setSidebarHidden(hidden)}
sidebar={sidebarContent}
>
{workspaceBody}
</SidebarLayout>
)}
<SidebarLayout
width={width ?? 250}
onWidthChange={setWidth}
hidden={sidebarHidden ?? false}
onHiddenChange={(hidden) => setSidebarHidden(hidden)}
floatingHidden={floatingSidebarHidden ?? true}
onFloatingHiddenChange={(hidden) => setFloatingSidebarHidden(hidden)}
onFloatingChange={setFloating}
sidebar={sidebarContent}
>
{workspaceBody}
</SidebarLayout>
</div>
);
}

View File

@@ -20,9 +20,10 @@ import { WorkspaceActionsDropdown } from './WorkspaceActionsDropdown';
interface Props {
className?: string;
floatingSidebar?: boolean;
}
export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Props) {
export const WorkspaceHeader = memo(function WorkspaceHeader({ className, floatingSidebar }: Props) {
const togglePalette = useToggleCommandPalette();
const [workspaceLayout, setWorkspaceLayout] = useAtom(workspaceLayoutAtom);
const workspace = useAtomValue(activeWorkspaceAtom);
@@ -41,7 +42,7 @@ export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Prop
)}
>
<HStack space={0.5} className={classNames('flex-1 pointer-events-none')}>
<SidebarActions />
<SidebarActions floating={floatingSidebar} />
<CookieDropdown />
<HStack className="min-w-0">
<WorkspaceActionsDropdown />