Files
openchamber/packages/ui/src/components/layout/TitlebarLeftControls.tsx
T
Bohdan Triapitsyn 2c30af5807 refactor: remove macOS vibrancy support and unify glass surfaces
Drops the macOS vibrancy toggle, IPC, and related settings copy
Updates dialogs, popovers, tooltips, and dropdowns to use shared glass styles
Adds sticky header fade behavior to model picker and sidebar lists
2026-08-10 15:10:37 +03:00

137 lines
5.4 KiB
TypeScript

import React from 'react';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { Icon } from '@/components/icon/Icon';
import { cn } from '@/lib/utils';
import { useUIStore } from '@/stores/useUIStore';
import { useI18n } from '@/lib/i18n';
import { useProjectActionsContext } from '@/hooks/useProjectActionsContext';
import { ProjectActionsButton } from '@/components/layout/ProjectActionsButton';
import { WindowsWindowControls } from '@/components/desktop/WindowsWindowControls';
import { formatShortcutForDisplay, getEffectiveShortcutCombo } from '@/lib/shortcuts';
import { invokeDesktop } from '@/lib/desktop';
import { useDesktopWindowControlsLayout } from '@/hooks/useDesktopWindowControlsLayout';
const ICON_BUTTON_CLASS =
'app-region-no-drag inline-flex h-8 w-8 items-center justify-center gap-2 rounded-md typography-ui-label font-medium text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary hover:bg-interactive-hover transition-colors';
/**
* Persistent top-left titlebar controls (sidebar toggle + project actions).
*
* Rendered exactly once as an absolutely-positioned overlay above both the
* sidebar and the header, so the buttons never migrate / re-mount between the
* two while the sidebar animates open or closed — the panels slide *underneath*
* a fixed control cluster instead. Its height tracks `--oc-header-height` and
* its left padding clears the OS window controls via `--oc-titlebar-left-inset`.
* The cluster's measured width is published as `--oc-titlebar-controls-width`
* so the header can reserve matching space when the sidebar is collapsed.
*/
export const TitlebarLeftControls: React.FC = () => {
const { t } = useI18n();
const toggleSidebar = useUIStore((state) => state.toggleSidebar);
const shortcutOverrides = useUIStore((state) => state.shortcutOverrides);
const projectActionsContext = useProjectActionsContext();
const clusterRef = React.useRef<HTMLDivElement | null>(null);
const toggleShortcut = formatShortcutForDisplay(getEffectiveShortcutCombo('toggle_sidebar', shortcutOverrides));
const { usesFramelessChrome, side: windowControlsSide } = useDesktopWindowControlsLayout();
const handleOpenWindowsAppMenu = React.useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
const rect = event.currentTarget.getBoundingClientRect();
void invokeDesktop('desktop_show_app_menu', {
x: rect.left,
y: rect.bottom,
}).catch((error) => {
console.warn('[titlebar] failed to open app menu', error);
});
}, []);
React.useEffect(() => {
if (typeof document === 'undefined') {
return;
}
const node = clusterRef.current;
if (!node) {
return;
}
const publishWidth = () => {
// Prefer scrollWidth so negative child margins / overflow cannot under-report
// the space the overlay actually occupies over the header.
const width = Math.max(node.getBoundingClientRect().width, node.scrollWidth);
document.documentElement.style.setProperty('--oc-titlebar-controls-width', `${Math.round(width)}px`);
};
publishWidth();
if (typeof ResizeObserver === 'undefined') {
return;
}
const observer = new ResizeObserver(publishWidth);
observer.observe(node);
return () => {
observer.disconnect();
};
}, []);
return (
// The overlay is a CSS no-drag zone so its buttons stay clickable. The
// header / sidebar strip beneath carve a matching no-drag region under it
// and remain drag regions everywhere else, so window dragging still works
// in the empty parts of the strip.
<div
className="app-region-no-drag absolute left-0 top-0 z-30 flex select-none items-center pr-2"
style={{
height: 'var(--oc-header-height, 3rem)',
paddingLeft: 'var(--oc-titlebar-left-inset, 0.75rem)',
}}
>
<div ref={clusterRef} className="flex items-center gap-2">
{usesFramelessChrome && windowControlsSide === 'left' ? (
<WindowsWindowControls visible position="left" />
) : null}
{usesFramelessChrome ? (
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
onClick={handleOpenWindowsAppMenu}
aria-label={t('header.actions.openAppMenuAria')}
className={cn(ICON_BUTTON_CLASS, 'shrink-0')}
>
<Icon name="menu-2" className="h-[18px] w-[18px]" />
</button>
</TooltipTrigger>
<TooltipContent>
<p>{t('header.actions.openAppMenu')}</p>
</TooltipContent>
</Tooltip>
) : null}
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
onClick={toggleSidebar}
aria-label={t('header.actions.openSessionsAria')}
className={cn(ICON_BUTTON_CLASS, 'shrink-0')}
>
<Icon name="layout-left" className="h-[18px] w-[18px]" />
</button>
</TooltipTrigger>
<TooltipContent>
<p>{t('header.actions.openSessionsWithShortcut', { shortcut: toggleShortcut })}</p>
</TooltipContent>
</Tooltip>
{projectActionsContext ? (
<ProjectActionsButton
projectRef={projectActionsContext.projectRef}
directory={projectActionsContext.directory}
/>
) : null}
</div>
</div>
);
};