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:
@@ -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}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user