From 54985365fe0c9d69f568207d5f616d4cb609bfb5 Mon Sep 17 00:00:00 2001 From: guantw Date: Sun, 6 Sep 2026 10:33:21 +0800 Subject: [PATCH] fix(web): adapt caption buttons to Windows styling Use Windows-specific caption glyphs and 46px rectangular hit areas for minimize, maximize/restore, and close in the desktop shell. Extend the Windows controls through the scene gutter and apply the existing danger and contrast tokens to the close hover state. Gate the glyphs and layout behind the Windows desktop runtime check so other platforms retain their existing appearance and command callbacks. --- .../components/SceneTopBar/SceneTopBar.scss | 11 +++++++ .../WindowControls/WindowControls.scss | 32 +++++++++++++++++++ .../WindowControls/WindowControls.tsx | 20 +++++++++--- 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/web-ui/src/app/components/SceneTopBar/SceneTopBar.scss b/src/web-ui/src/app/components/SceneTopBar/SceneTopBar.scss index 338d6d80a3..e1dd669c6a 100644 --- a/src/web-ui/src/app/components/SceneTopBar/SceneTopBar.scss +++ b/src/web-ui/src/app/components/SceneTopBar/SceneTopBar.scss @@ -13,6 +13,17 @@ container-type: inline-size; user-select: none; + &:has(.window-controls--windows) { + // Extend through the scene surface's right gutter so Close reaches the edge. + width: calc(100% + var(--openbitfun-space-4)); + padding-block: 0; + padding-inline-end: 0; + + > [data-openbitfun-part='trailing'] { + align-self: stretch; + } + } + &[data-bordered='true'] { &::before, &::after { diff --git a/src/web-ui/src/app/components/WindowControls/WindowControls.scss b/src/web-ui/src/app/components/WindowControls/WindowControls.scss index ac193e59aa..6b50e2aa15 100644 --- a/src/web-ui/src/app/components/WindowControls/WindowControls.scss +++ b/src/web-ui/src/app/components/WindowControls/WindowControls.scss @@ -135,6 +135,38 @@ } } +// Windows caption buttons keep their full hit area even in a narrow window. +.window-controls--windows { + align-items: stretch; + gap: 0; + height: 100%; + + .window-controls__btn { + flex: 0 0 46px; + width: 46px; + height: 100%; + border: 0; + border-radius: 0; + color: var(--openbitfun-color-content-primary); + cursor: default; + + svg { + width: 12px; + height: 12px; + transition: none; + } + + &:focus-visible { + outline-offset: -2px; + } + + &--close:hover:not(:disabled) { + background: var(--openbitfun-color-status-danger-content); + color: var(--openbitfun-color-content-on-dark); + } + } +} + @media (prefers-reduced-motion: reduce) { .window-controls__btn { transition: none; diff --git a/src/web-ui/src/app/components/WindowControls/WindowControls.tsx b/src/web-ui/src/app/components/WindowControls/WindowControls.tsx index e74d8fb37f..d564e39a41 100644 --- a/src/web-ui/src/app/components/WindowControls/WindowControls.tsx +++ b/src/web-ui/src/app/components/WindowControls/WindowControls.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Tooltip } from '@openbitfun/ui'; import { useTranslation } from 'react-i18next'; +import { isWindowsDesktopRuntime } from '@/infrastructure/runtime'; import './WindowControls.scss'; export interface WindowControlsProps @@ -38,6 +39,12 @@ const CloseGlyph = () => ( ); +const WindowsGlyph = ({ d }: { d: string }) => ( + +); + /** Desktop-shell window commands. This is product chrome, not a public UI primitive. */ export const WindowControls: React.FC = ({ onMinimize, @@ -49,6 +56,7 @@ export const WindowControls: React.FC = ({ ...props }) => { const { t } = useTranslation('common'); + const isWindows = isWindowsDesktopRuntime(); const maximizeLabel = maximized ? t('window.restore') : t('window.maximize'); const run = (event: React.MouseEvent, command: () => void) => { @@ -60,7 +68,7 @@ export const WindowControls: React.FC = ({ return (
= ({ disabled={disabled} aria-label={t('window.minimize')} > - + {isWindows ? : } @@ -85,7 +93,11 @@ export const WindowControls: React.FC = ({ disabled={disabled} aria-label={maximizeLabel} > - {maximized ? : } + {isWindows ? ( + + ) : maximized ? : } @@ -97,7 +109,7 @@ export const WindowControls: React.FC = ({ disabled={disabled} aria-label={t('window.close')} > - + {isWindows ? : }