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
@@ -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>