Keep workspace focus mode scoped and easy to exit (#2151)

* fix(workspace): keep focus mode scoped and easy to exit

Route focus mode through the active workspace so persisted state cannot hide chrome on settings or other screens. Add a visible exit control and keep desktop window chrome aligned and visually quiet.

* fix(app): clarify muted chrome semantics
This commit is contained in:
Mohamed Boudra
2026-07-16 23:46:24 +02:00
committed by GitHub
parent d2308f4835
commit a622860a3e
25 changed files with 261 additions and 30 deletions
+20 -8
View File
@@ -40,6 +40,7 @@ import { FloatingPanelPortalHost } from "@/components/ui/floating-panel-portal";
import { HostChooserModal, useHostChooser } from "@/hosts/host-chooser";
import {
getIsElectronRuntime,
getIsElectronRuntimeMac,
HEADER_INNER_HEIGHT,
useIsCompactFormFactor,
} from "@/constants/layout";
@@ -108,7 +109,11 @@ import {
WindowChromeRegion,
WindowChromeSafeArea,
} from "@/utils/desktop-window";
import { buildOpenProjectRoute, parseServerIdFromPathname } from "@/utils/host-routes";
import {
buildOpenProjectRoute,
parseHostWorkspaceRouteFromPathname,
parseServerIdFromPathname,
} from "@/utils/host-routes";
import { buildNotificationRoute, resolveNotificationTarget } from "@/utils/notification-routing";
import { navigateToAgent } from "@/utils/navigate-to-agent";
import {
@@ -425,7 +430,6 @@ function AppContainer({ children, chromeEnabled: chromeEnabledOverride }: AppCon
const openDesktopAgentList = usePanelStore((state) => state.openDesktopAgentList);
const closeDesktopAgentList = usePanelStore((state) => state.closeDesktopAgentList);
const closeDesktopFileExplorer = usePanelStore((state) => state.closeDesktopFileExplorer);
const toggleFocusMode = usePanelStore((state) => state.toggleFocusMode);
const isFocusModeEnabled = usePanelStore((state) => state.desktop.focusModeEnabled);
const isDesktopAgentListOpen = usePanelStore((state) => state.desktop.agentListOpen);
const isDesktopFileExplorerOpen = usePanelStore((state) => state.desktop.fileExplorerOpen);
@@ -442,6 +446,8 @@ function AppContainer({ children, chromeEnabled: chromeEnabledOverride }: AppCon
const isCompactLayout = useIsCompactFormFactor();
useCompactWebViewportZoomLock(isCompactLayout);
const pathname = usePathname();
const isWorkspaceRoute = parseHostWorkspaceRouteFromPathname(pathname) !== null;
const isWorkspaceFocusModeEnabled = isWorkspaceRoute && isFocusModeEnabled;
const chromeEnabled = chromeEnabledOverride ?? daemons.length > 0;
const toggleAgentList = isCompactLayout ? toggleMobileAgentList : toggleDesktopAgentList;
const toggleDesktopSidebars = useCallback(() => {
@@ -470,7 +476,6 @@ function AppContainer({ children, chromeEnabled: chromeEnabledOverride }: AppCon
isMobile: isCompactLayout,
toggleAgentList,
toggleBothSidebars: toggleDesktopSidebars,
toggleFocusMode,
cycleTheme,
});
@@ -479,11 +484,11 @@ function AppContainer({ children, chromeEnabled: chromeEnabledOverride }: AppCon
const appContentMinimumWidth = resolveDesktopAppContentMinimum({
isSettingsRoute: pathname.includes("/settings"),
isWorkspaceExplorerOpen: pathname.includes("/workspace/") && isDesktopFileExplorerOpen,
isWorkspaceExplorerOpen: isWorkspaceRoute && isDesktopFileExplorerOpen,
requestedExplorerWidth: explorerWidth,
viewportWidth,
});
const desktopSidebarMounted = chromeEnabled && !isFocusModeEnabled;
const desktopSidebarMounted = chromeEnabled && !isWorkspaceFocusModeEnabled;
const desktopSidebarVisible =
!isCompactLayout &&
desktopSidebarMounted &&
@@ -497,7 +502,7 @@ function AppContainer({ children, chromeEnabled: chromeEnabledOverride }: AppCon
const appChromeLayout = resolveDesktopAppChromeLayout({
desktopSidebarRendered: desktopSidebarVisible,
hasTopLeftWindowControls,
sidebarControlsEnabled: chromeEnabled && !isFocusModeEnabled,
sidebarControlsEnabled: chromeEnabled && !isWorkspaceFocusModeEnabled,
});
const sidebarChrome = (
<SidebarChrome
@@ -659,16 +664,23 @@ function DesktopWindowControlsSync({ enabled }: { enabled: boolean }) {
const { theme } = useUnistyles();
const surface0 = theme.colors.surface0;
const foreground = theme.colors.foreground;
const pathname = usePathname();
const isFocusModeEnabled = usePanelStore((state) => state.desktop.focusModeEnabled);
const liftTrafficLights =
getIsElectronRuntimeMac() &&
isFocusModeEnabled &&
parseHostWorkspaceRouteFromPathname(pathname) !== null;
useEffect(() => {
if (!enabled || isNative) return;
void updateDesktopWindowControls({
backgroundColor: surface0,
foregroundColor: foreground,
trafficLightOffsetY: liftTrafficLights ? -5 : 0.5,
}).catch((error) => {
console.warn("[DesktopWindow] Failed to update window controls overlay", error);
});
}, [enabled, surface0, foreground]);
}, [enabled, surface0, foreground, liftTrafficLights]);
return null;
}
@@ -987,7 +999,7 @@ const layoutStyles = StyleSheet.create((theme) => ({
},
windowSidebarToggle: {
position: "absolute",
top: 0,
top: 1,
left: 0,
zIndex: 20,
height: HEADER_INNER_HEIGHT,
@@ -45,12 +45,14 @@ function MobileMenuIcon({ color }: { color: string }) {
function SidebarMenuToggleButton({
isMobile,
extraMutedIdleIcon = false,
resolvedStyle,
tooltipSide = "right",
testID = "menu-button",
nativeID = "menu-button",
}: Omit<SidebarMenuToggleProps, "style"> & {
isMobile: boolean;
extraMutedIdleIcon?: boolean;
resolvedStyle: StyleProp<ViewStyle>;
}) {
const { theme } = useUnistyles();
@@ -83,7 +85,12 @@ function SidebarMenuToggleButton({
accessibilityState={accessibilityState}
>
{({ hovered, pressed }) => {
const color = hovered || pressed ? theme.colors.foreground : theme.colors.foregroundMuted;
let color = extraMutedIdleIcon
? theme.colors.foregroundExtraMuted
: theme.colors.foregroundMuted;
if (hovered || pressed) {
color = theme.colors.foreground;
}
return isMobile ? (
<MobileMenuIcon color={color} />
) : (
@@ -121,7 +128,14 @@ export function SidebarMenuToggle({ style, ...props }: SidebarMenuToggleProps =
export function WindowSidebarMenuToggle({ style, ...props }: SidebarMenuToggleProps = {}) {
const resolvedStyle = useMemo(() => [styles.leadingToggle, style], [style]);
return <SidebarMenuToggleButton {...props} isMobile={false} resolvedStyle={resolvedStyle} />;
return (
<SidebarMenuToggleButton
{...props}
isMobile={false}
extraMutedIdleIcon
resolvedStyle={resolvedStyle}
/>
);
}
export function MenuHeader({ title, rightContent, borderless }: MenuHeaderProps) {
@@ -123,6 +123,7 @@ interface SplitContainerProps {
onReorderTabsInPane: (paneId: string, tabIds: string[]) => void;
renderPaneEmptyState?: () => ReactNode;
focusModeEnabled?: boolean;
onExitFocusMode: () => void;
}
interface WorkspaceTabDragData {
@@ -390,6 +391,7 @@ export function SplitContainer({
onReorderTabsInPane,
renderPaneEmptyState = () => null,
focusModeEnabled,
onExitFocusMode,
}: SplitContainerProps) {
const inheritedWindowChromeCorners = useWindowChromeCorners();
const windowChromeCorners = focusModeEnabled ? inheritedWindowChromeCorners : "none";
@@ -611,6 +613,8 @@ export function SplitContainer({
dropPreview={dropPreview}
tabDropPreview={tabDropPreview}
windowChromeCorners={splitRoot.usesFallbackStrip ? "none" : windowChromeCorners}
focusModeEnabled={focusModeEnabled}
onExitFocusMode={onExitFocusMode}
/>
<DragOverlay dropAnimation={null}>
{activeDragTabId ? (
@@ -755,6 +759,8 @@ function SplitNodeView({
dropPreview,
tabDropPreview,
windowChromeCorners,
focusModeEnabled,
onExitFocusMode,
}: SplitNodeViewProps) {
const groupId = node.kind === "group" ? node.group.id : null;
const groupDirection = node.kind === "group" ? node.group.direction : null;
@@ -808,6 +814,8 @@ function SplitNodeView({
showDropZones={showDropZones}
dropPreview={dropPreview}
tabDropPreview={tabDropPreview}
focusModeEnabled={focusModeEnabled}
onExitFocusMode={onExitFocusMode}
/>
</WindowChromeRegion>
);
@@ -857,6 +865,8 @@ function SplitNodeView({
dropPreview={dropPreview}
tabDropPreview={tabDropPreview}
windowChromeCorners={windowChromeCorners}
focusModeEnabled={focusModeEnabled}
onExitFocusMode={onExitFocusMode}
/>
</SplitGroupChild>
{index < node.group.children.length - 1 ? (
@@ -908,6 +918,8 @@ function SplitPaneView({
showDropZones,
dropPreview,
tabDropPreview,
focusModeEnabled,
onExitFocusMode,
}: SplitPaneViewProps) {
const { theme: _theme } = useUnistyles();
const paneRef = useRef<View | null>(null);
@@ -1043,6 +1055,8 @@ function SplitPaneView({
tabDropPreviewIndex={
tabDropPreview?.paneId === pane.id ? tabDropPreview.indicatorIndex : null
}
focusModeEnabled={Boolean(focusModeEnabled)}
onExitFocusMode={onExitFocusMode}
/>
</WindowChromeSafeArea>
+1
View File
@@ -95,6 +95,7 @@ export interface DesktopWindowControlsOverlayUpdate {
height?: number;
backgroundColor?: string;
foregroundColor?: string;
trafficLightOffsetY?: number;
}
export interface DesktopWindowBridge {
@@ -34,14 +34,12 @@ export function useKeyboardShortcuts({
isMobile,
toggleAgentList,
toggleBothSidebars,
toggleFocusMode,
cycleTheme,
}: {
enabled: boolean;
isMobile: boolean;
toggleAgentList: () => void;
toggleBothSidebars?: () => void;
toggleFocusMode?: () => void;
cycleTheme?: () => void;
}) {
const pathname = usePathname();
@@ -105,7 +103,6 @@ export function useKeyboardShortcuts({
const callbacksByName: Record<ShortcutCallbackName, (() => void) | undefined> = {
"toggle-agent-list": toggleAgentList,
"toggle-both-sidebars": toggleBothSidebars,
"toggle-focus-mode": toggleFocusMode,
"cycle-theme": cycleTheme,
};
@@ -305,6 +302,5 @@ export function useKeyboardShortcuts({
router,
toggleAgentList,
toggleBothSidebars,
toggleFocusMode,
]);
}
+1
View File
@@ -499,6 +499,7 @@ export const ar: TranslationResources = {
preparingTerminal: "إعداد علامة التبويب المحطة الطرفية",
preparingTerminalTooltip: "جارٍ تحضير المحطة...",
newBrowser: "متصفح جديد",
exitFocusMode: "إنهاء وضع التركيز",
splitRight: "تقسيم الجزء الأيمن",
splitDown: "تقسيم الجزء لأسفل",
terminalProfilesMenu: "Terminal profiles",
+1
View File
@@ -498,6 +498,7 @@ export const en = {
preparingTerminal: "Preparing terminal tab",
preparingTerminalTooltip: "Preparing terminal...",
newBrowser: "New browser",
exitFocusMode: "Exit focus mode",
splitRight: "Split pane right",
splitDown: "Split pane down",
terminalProfilesMenu: "Terminal profiles",
+1
View File
@@ -504,6 +504,7 @@ export const es: TranslationResources = {
preparingTerminal: "Preparando la pestaña del terminal",
preparingTerminalTooltip: "Preparando terminal...",
newBrowser: "Nuevo navegador",
exitFocusMode: "Salir del modo de concentración",
splitRight: "Panel dividido a la derecha",
splitDown: "Dividir panel hacia abajo",
terminalProfilesMenu: "Terminal profiles",
+1
View File
@@ -503,6 +503,7 @@ export const fr: TranslationResources = {
preparingTerminal: "Préparation de l'onglet du terminal",
preparingTerminalTooltip: "Préparation du terminal...",
newBrowser: "Nouveau navigateur",
exitFocusMode: "Quitter le mode concentration",
splitRight: "Volet divisé à droite",
splitDown: "Diviser le volet vers le bas",
terminalProfilesMenu: "Terminal profiles",
+1
View File
@@ -504,6 +504,7 @@ export const ja: TranslationResources = {
preparingTerminal: "ターミナルタブを準備中",
preparingTerminalTooltip: "ターミナルを準備中...",
newBrowser: "新しいブラウザ",
exitFocusMode: "フォーカスモードを終了",
splitRight: "右にペインを分割",
splitDown: "下にペインを分割",
terminalProfilesMenu: "ターミナルプロファイル",
+1
View File
@@ -503,6 +503,7 @@ export const ptBR: TranslationResources = {
preparingTerminal: "Preparando aba de terminal",
preparingTerminalTooltip: "Preparando terminal...",
newBrowser: "Novo navegador",
exitFocusMode: "Sair do modo de foco",
splitRight: "Dividir painel à direita",
splitDown: "Dividir painel abaixo",
terminalProfilesMenu: "Perfis de terminal",
+1
View File
@@ -503,6 +503,7 @@ export const ru: TranslationResources = {
preparingTerminal: "Подготовка вкладки терминала",
preparingTerminalTooltip: "Подготовка терминала...",
newBrowser: "Новый браузер",
exitFocusMode: "Выйти из режима фокусировки",
splitRight: "Разделить панель справа",
splitDown: "Разделить панель вниз",
terminalProfilesMenu: "Terminal profiles",
+1
View File
@@ -499,6 +499,7 @@ export const zhCN: TranslationResources = {
preparingTerminal: "正在准备 Terminal 标签",
preparingTerminalTooltip: "正在准备 Terminal...",
newBrowser: "新建浏览器",
exitFocusMode: "退出专注模式",
splitRight: "向右拆分窗格",
splitDown: "向下拆分窗格",
terminalProfilesMenu: "Terminal profiles",
@@ -25,6 +25,7 @@ export type KeyboardActionId =
| "workspace.pane.move-tab.up"
| "workspace.pane.move-tab.down"
| "workspace.pane.close"
| "workspace.focus.toggle"
| "workspace.terminal.new"
| "sidebar.toggle.right"
| "workspace.new"
@@ -57,6 +58,7 @@ export type KeyboardActionDefinition =
| { id: "workspace.pane.move-tab.up"; scope: KeyboardActionScope }
| { id: "workspace.pane.move-tab.down"; scope: KeyboardActionScope }
| { id: "workspace.pane.close"; scope: KeyboardActionScope }
| { id: "workspace.focus.toggle"; scope: KeyboardActionScope }
| { id: "workspace.terminal.new"; scope: KeyboardActionScope }
| { id: "sidebar.toggle.right"; scope: KeyboardActionScope }
| { id: "workspace.new"; scope: KeyboardActionScope }
@@ -47,6 +47,7 @@ describe("routeKeyboardShortcut — dispatch passthroughs", () => {
["workspace.pane.move-tab.up", { id: "workspace.pane.move-tab.up", scope: "workspace" }],
["workspace.pane.move-tab.down", { id: "workspace.pane.move-tab.down", scope: "workspace" }],
["workspace.pane.close", { id: "workspace.pane.close", scope: "workspace" }],
["view.toggle.focus", { id: "workspace.focus.toggle", scope: "workspace" }],
])("%s → dispatch %j", (action, expected) => {
expect(routeKeyboardShortcut({ action, payload: null }, makeCtx())).toEqual({
kind: "dispatch",
@@ -333,7 +334,6 @@ describe("routeKeyboardShortcut — callbacks and pickers", () => {
it.each([
["sidebar.toggle.left", "toggle-agent-list"],
["sidebar.toggle.both", "toggle-both-sidebars"],
["view.toggle.focus", "toggle-focus-mode"],
["theme.cycle", "cycle-theme"],
] as const)("%s → callback %s", (action, name) => {
expect(routeKeyboardShortcut({ action, payload: null }, makeCtx())).toEqual<ShortcutAction>({
+2 -6
View File
@@ -20,11 +20,7 @@ export interface ShortcutRoutingInput {
payload: KeyboardShortcutPayload;
}
export type ShortcutCallbackName =
| "toggle-agent-list"
| "toggle-both-sidebars"
| "toggle-focus-mode"
| "cycle-theme";
export type ShortcutCallbackName = "toggle-agent-list" | "toggle-both-sidebars" | "cycle-theme";
export type ShortcutAction =
| { kind: "none" }
@@ -63,12 +59,12 @@ const PASSTHROUGH_DISPATCH: Record<string, KeyboardActionDefinition> = {
"workspace.pane.move-tab.up": { id: "workspace.pane.move-tab.up", scope: "workspace" },
"workspace.pane.move-tab.down": { id: "workspace.pane.move-tab.down", scope: "workspace" },
"workspace.pane.close": { id: "workspace.pane.close", scope: "workspace" },
"view.toggle.focus": { id: "workspace.focus.toggle", scope: "workspace" },
};
const SIMPLE_CALLBACKS: Record<string, ShortcutCallbackName> = {
"sidebar.toggle.left": "toggle-agent-list",
"sidebar.toggle.both": "toggle-both-sidebars",
"view.toggle.focus": "toggle-focus-mode",
"theme.cycle": "cycle-theme",
};
+6 -1
View File
@@ -1081,7 +1081,12 @@ function SettingsSidebar({
);
return (
<View style={outerContainerStyle} testID="settings-sidebar">
<View
accessibilityLabel={t("settings.title")}
role="navigation"
style={outerContainerStyle}
testID="settings-sidebar"
>
{isDesktop ? (
<View style={innerContainerStyle}>
<View style={sidebarStyles.sidebarDragArea}>
@@ -438,6 +438,8 @@ interface WorkspaceDesktopTabsRowProps {
activeDragTabId?: string | null;
tabDropPreviewIndex?: number | null;
showPaneSplitActions?: boolean;
focusModeEnabled: boolean;
onExitFocusMode: () => void;
}
function getFallbackTabLabel(
@@ -758,15 +760,19 @@ export function WorkspaceDesktopTabsRow({
activeDragTabId = null,
tabDropPreviewIndex = null,
showPaneSplitActions = true,
focusModeEnabled,
onExitFocusMode,
}: WorkspaceDesktopTabsRowProps) {
const { t } = useTranslation();
const router = useRouter();
const newTabKeys = useShortcutKeys("workspace-tab-new");
const focusModeKeys = useShortcutKeys("toggle-focus");
const splitRightKeys = useShortcutKeys("workspace-pane-split-right");
const splitDownKeys = useShortcutKeys("workspace-pane-split-down");
const [tabsContainerWidth, setTabsContainerWidth] = useState<number>(0);
const [tabsActionsWidth, setTabsActionsWidth] = useState<number>(0);
const [inlineAddButtonWidth, setInlineAddButtonWidth] = useState<number>(0);
const [exitFocusModeWidth, setExitFocusModeWidth] = useState<number>(0);
const handleTabsContainerLayout = useCallback((event: LayoutChangeEvent) => {
updateMeasuredWidth(setTabsContainerWidth, event);
@@ -780,12 +786,18 @@ export function WorkspaceDesktopTabsRow({
updateMeasuredWidth(setInlineAddButtonWidth, event);
}, []);
const handleExitFocusModeLayout = useCallback((event: LayoutChangeEvent) => {
updateMeasuredWidth(setExitFocusModeWidth, event);
}, []);
const layoutMetrics = useMemo(
() => ({
rowHorizontalInset: 0,
actionsReservedWidth: Math.max(
0,
tabsActionsWidth + (inlineAddButtonWidth || DEFAULT_INLINE_ADD_BUTTON_RESERVED_WIDTH),
tabsActionsWidth +
(inlineAddButtonWidth || DEFAULT_INLINE_ADD_BUTTON_RESERVED_WIDTH) +
(focusModeEnabled ? exitFocusModeWidth : 0),
),
rowPaddingHorizontal: 0,
tabGap: 0,
@@ -795,7 +807,7 @@ export function WorkspaceDesktopTabsRow({
estimatedCharWidth: 7,
closeButtonWidth: 22,
}),
[inlineAddButtonWidth, tabsActionsWidth],
[exitFocusModeWidth, focusModeEnabled, inlineAddButtonWidth, tabsActionsWidth],
);
const fallbackTabLabels = useMemo(
@@ -968,6 +980,31 @@ export function WorkspaceDesktopTabsRow({
testID="workspace-tabs-row"
onLayout={handleTabsContainerLayout}
>
{focusModeEnabled ? (
<View style={styles.exitFocusModeSlot} onLayout={handleExitFocusModeLayout}>
<Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile={false}>
<TooltipTrigger
testID="workspace-exit-focus-mode"
onPress={onExitFocusMode}
accessibilityRole="button"
accessibilityLabel={t("workspace.tabs.actions.exitFocusMode")}
style={inlineAddActionButtonStyle}
>
<ThemedX size={14} uniProps={mutedColorMapping} />
</TooltipTrigger>
<TooltipContent side="bottom" align="center" offset={8}>
<View style={styles.newTabTooltipRow}>
<Text style={styles.newTabTooltipText}>
{t("workspace.tabs.actions.exitFocusMode")}
</Text>
{focusModeKeys ? (
<Shortcut chord={focusModeKeys} style={styles.newTabTooltipShortcut} />
) : null}
</View>
</TooltipContent>
</Tooltip>
</View>
) : null}
<ScrollView
horizontal
scrollEnabled={layout.requiresHorizontalScrollFallback}
@@ -1184,6 +1221,14 @@ const styles = StyleSheet.create((theme) => ({
alignItems: "center",
paddingHorizontal: theme.spacing[2],
},
exitFocusModeSlot: {
alignSelf: "stretch",
flexDirection: "row",
alignItems: "center",
paddingHorizontal: theme.spacing[1],
borderRightWidth: 1,
borderRightColor: theme.colors.border,
},
inlineAddButton: {
flexDirection: "row",
alignItems: "center",
@@ -266,6 +266,9 @@ const ThemedDynamicProviderIcon = withUnistyles(DynamicProviderIcon);
const foregroundColorMapping = (theme: Theme) => ({ color: theme.colors.foreground });
const mutedColorMapping = (theme: Theme) => ({ color: theme.colors.foregroundMuted });
const extraMutedColorMapping = (theme: Theme) => ({
color: theme.colors.foregroundExtraMuted,
});
const sourceControlPanelStrokeWidth15 = { strokeWidth: 1.5 };
@@ -1712,6 +1715,7 @@ function WorkspaceScreenContent({
const toast = useToast();
const isMobile = useIsCompactFormFactor();
const isFocusModeEnabled = usePanelStore((state) => state.desktop.focusModeEnabled);
const toggleFocusMode = usePanelStore((state) => state.toggleFocusMode);
const normalizedServerId = useMemo(() => trimNonEmpty(decodeSegment(serverId)) ?? "", [serverId]);
@@ -2966,6 +2970,11 @@ function WorkspaceScreenContent({
const handleWorkspacePaneAction = useCallback(
(action: KeyboardActionDefinition): boolean => {
if (action.id === "workspace.focus.toggle") {
toggleFocusMode();
return true;
}
if (!persistenceKey || !workspaceLayout) {
return true;
}
@@ -3039,6 +3048,7 @@ function WorkspaceScreenContent({
persistenceKey,
focusedPaneTabState.activeTabId,
focusedPaneTabState.pane,
toggleFocusMode,
workspaceLayout,
],
);
@@ -3072,6 +3082,7 @@ function WorkspaceScreenContent({
"workspace.pane.move-tab.up",
"workspace.pane.move-tab.down",
"workspace.pane.close",
"workspace.focus.toggle",
] as const,
enabled: Boolean(isRouteFocused && normalizedServerId && normalizedWorkspaceId),
priority: 100,
@@ -3366,8 +3377,8 @@ function WorkspaceScreenContent({
style={explorerToggleStyle}
>
{({ hovered, pressed }) => {
const active = isExplorerOpen || hovered || pressed;
const colorMapping = active ? foregroundColorMapping : mutedColorMapping;
const active = hovered || pressed;
const colorMapping = active ? foregroundColorMapping : extraMutedColorMapping;
return (
<>
<ThemedSourceControlPanelIcon size={16} uniProps={colorMapping} />
@@ -3412,9 +3423,9 @@ function WorkspaceScreenContent({
accessibilityLabel={explorerToggleLabel}
accessibilityState={explorerToggleAccessibilityState}
>
{({ hovered }) => {
{({ hovered, pressed }) => {
const colorMapping =
isExplorerOpen || hovered ? foregroundColorMapping : mutedColorMapping;
hovered || pressed ? foregroundColorMapping : extraMutedColorMapping;
return <ThemedPanelRight size={16} uniProps={colorMapping} />;
}}
</HeaderToggleButton>
@@ -3502,6 +3513,7 @@ function WorkspaceScreenContent({
<SplitContainer
layout={workspaceLayout}
focusModeEnabled={desktopFocusModeEnabled}
onExitFocusMode={toggleFocusMode}
workspaceKey={persistenceKey}
normalizedServerId={normalizedServerId}
normalizedWorkspaceId={normalizedWorkspaceId}
@@ -3539,6 +3551,7 @@ function WorkspaceScreenContent({
workspaceLayout,
persistenceKey,
desktopFocusModeEnabled,
toggleFocusMode,
normalizedServerId,
normalizedWorkspaceId,
isRouteFocused,
@@ -3668,6 +3681,8 @@ function WorkspaceScreenContent({
onSplitRight={noop}
onSplitDown={noop}
showPaneSplitActions={false}
focusModeEnabled={desktopFocusModeEnabled}
onExitFocusMode={toggleFocusMode}
/>
) : null}
+8
View File
@@ -153,6 +153,7 @@ const lightSemanticColors = {
// Text
foreground: "#1a1a1e",
foregroundMuted: "#71717a",
foregroundExtraMuted: "#a1a1aa",
// Controls
scrollbarHandle: "#3f3f46", // zinc-700
@@ -231,6 +232,7 @@ interface DarkThemeConfig {
surfaceSidebar: string;
surfaceSidebarHover: string;
foregroundMuted: string;
foregroundExtraMuted: string;
scrollbarHandle: string;
border: string;
borderAccent: string;
@@ -271,6 +273,7 @@ function buildDarkSemanticColors(tint: DarkThemeConfig) {
foreground: "#fafafa",
foregroundMuted: tint.foregroundMuted,
foregroundExtraMuted: tint.foregroundExtraMuted,
scrollbarHandle: tint.scrollbarHandle,
@@ -332,6 +335,7 @@ const paseoDarkColors = buildDarkSemanticColors({
surfaceSidebar: "#141716",
surfaceSidebarHover: "#1c1f1e",
foregroundMuted: "#A1A5A4",
foregroundExtraMuted: "#717574",
scrollbarHandle: "#717574",
border: "#252B2A",
borderAccent: "#2F3534",
@@ -351,6 +355,7 @@ const zincDarkColors = buildDarkSemanticColors({
surfaceSidebar: "#131316",
surfaceSidebarHover: "#1b1b1e",
foregroundMuted: "#a1a1aa",
foregroundExtraMuted: "#71717a",
scrollbarHandle: "#71717a",
border: "#27272a",
borderAccent: "#303036",
@@ -371,6 +376,7 @@ const midnightDarkColors = buildDarkSemanticColors({
surfaceSidebar: "#121420",
surfaceSidebarHover: "#1a1c28",
foregroundMuted: "#9a9db0",
foregroundExtraMuted: "#6b6e82",
scrollbarHandle: "#6b6e82",
border: "#242636",
borderAccent: "#2e3040",
@@ -390,6 +396,7 @@ const claudeDarkColors = buildDarkSemanticColors({
surfaceSidebar: "#1a1918",
surfaceSidebarHover: "#222120",
foregroundMuted: "#ada9a5",
foregroundExtraMuted: "#78746f",
scrollbarHandle: "#78746f",
border: "#2c2a27",
borderAccent: "#36332f",
@@ -409,6 +416,7 @@ const ghosttyDarkColors = buildDarkSemanticColors({
surfaceSidebar: "#21252d",
surfaceSidebarHover: "#292d36",
foregroundMuted: "#c8ccd8",
foregroundExtraMuted: "#a0a4b2",
scrollbarHandle: "#a0a4b2",
border: "#353a47",
borderAccent: "#3f4454",