From e23c122ec295bfa5b6a285de4e22ba24f058e052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Wed, 1 Jul 2026 10:28:31 +0200 Subject: [PATCH 1/9] add custom window decorations for full view windows --- new-ui/src/app/App.tsx | 2 + .../shared/components/FloatingMenu/style.scss | 2 +- new-ui/src/shared/components/Menu/style.scss | 2 +- .../components/ModalFoundation/style.scss | 10 +- .../WindowDecorations/WindowDecorations.tsx | 162 ++++++++++++++++++ .../components/WindowDecorations/style.scss | 75 ++++++++ .../components/wizard/WizardPage/style.scss | 2 +- .../shared/layouts/FullPageLayout/style.scss | 2 +- new-ui/src/shared/scss/_shared_tokens.scss | 5 + src-tauri/capabilities/default.json | 1 + src-tauri/src/window_manager/macos.rs | 37 ++-- src-tauri/src/window_manager/mod.rs | 2 +- src-tauri/tauri.local.conf.json | 4 + 13 files changed, 286 insertions(+), 20 deletions(-) create mode 100644 new-ui/src/shared/components/WindowDecorations/WindowDecorations.tsx create mode 100644 new-ui/src/shared/components/WindowDecorations/style.scss diff --git a/new-ui/src/app/App.tsx b/new-ui/src/app/App.tsx index f73839e68..ec5f23f48 100644 --- a/new-ui/src/app/App.tsx +++ b/new-ui/src/app/App.tsx @@ -1,6 +1,7 @@ import { QueryClientProvider } from '@tanstack/react-query'; import { RouterProvider } from '@tanstack/react-router'; import { MainBackground } from '../shared/components/MainBackground/MainBackground'; +import { WindowDecorations } from '../shared/components/WindowDecorations/WindowDecorations'; import { queryClient } from './query'; import { router } from './router'; @@ -8,6 +9,7 @@ function App() { return (
+
diff --git a/new-ui/src/shared/components/FloatingMenu/style.scss b/new-ui/src/shared/components/FloatingMenu/style.scss index 05de2885b..e3e69ee13 100644 --- a/new-ui/src/shared/components/FloatingMenu/style.scss +++ b/new-ui/src/shared/components/FloatingMenu/style.scss @@ -4,5 +4,5 @@ padding: 8px; background-color: var(--c-saturated-dark-blue-60); box-shadow: 0 4px 12px 0 rgb(0 0 0 / 7%); - backdrop-filter: blur(4px); + backdrop-filter: blur(10px); } diff --git a/new-ui/src/shared/components/Menu/style.scss b/new-ui/src/shared/components/Menu/style.scss index ec2776f54..e948189a7 100644 --- a/new-ui/src/shared/components/Menu/style.scss +++ b/new-ui/src/shared/components/Menu/style.scss @@ -8,7 +8,7 @@ border: 0; background-color: var(--c-saturated-dark-blue-60); box-shadow: 0 4px 12px 0 rgb(0 0 0 / 7%); - backdrop-filter: blur(4px); + backdrop-filter: blur(10px); overflow: hidden auto; z-index: 5; diff --git a/new-ui/src/shared/components/ModalFoundation/style.scss b/new-ui/src/shared/components/ModalFoundation/style.scss index 2224e38a9..4bdf7e729 100644 --- a/new-ui/src/shared/components/ModalFoundation/style.scss +++ b/new-ui/src/shared/components/ModalFoundation/style.scss @@ -6,18 +6,22 @@ .backdrop { position: fixed; - inset: 0; + left: 0; + top: var(--window-decorations-height); display: block; content: ' '; width: 100%; - height: 100%; + height: calc(100dvh - var(--window-decorations-height)); z-index: 4; } .modal-positioner { overflow: auto; position: fixed; - inset: 0; + left: 0; + top: var(--window-decorations-height); + width: 100%; + height: calc(100dvh - var(--window-decorations-height)); z-index: 4; display: flex; flex-flow: column; diff --git a/new-ui/src/shared/components/WindowDecorations/WindowDecorations.tsx b/new-ui/src/shared/components/WindowDecorations/WindowDecorations.tsx new file mode 100644 index 000000000..02cda5be9 --- /dev/null +++ b/new-ui/src/shared/components/WindowDecorations/WindowDecorations.tsx @@ -0,0 +1,162 @@ +import './style.scss'; +import { getCurrentWindow } from '@tauri-apps/api/window'; +import { type as getOsType } from '@tauri-apps/plugin-os'; +import clsx from 'clsx'; +import { useEffect, useState } from 'react'; +import { WindowId } from '../../consts'; + +const osType = getOsType(); + +const isWindows = osType === 'windows'; +const isMac = osType === 'macos'; +const osCheck = isWindows || isMac; + +const appWindow = getCurrentWindow(); + +const isFullView = appWindow.label === WindowId.FullView; + +const decorationsHeight = 33; + +export const WindowDecorations = () => { + const [isMaximized, setIsMaximized] = useState(false); + const [isDecorated, setIsDecorated] = useState(true); + + useEffect(() => { + void appWindow.isDecorated().then(setIsDecorated); + }, []); + + useEffect(() => { + document.documentElement.style.setProperty( + '--window-decorations-height', + !isDecorated && isFullView && osCheck ? `${decorationsHeight}px` : '0', + ); + }, [isDecorated]); + + useEffect(() => { + if (!osCheck || !isFullView || isDecorated) return; + + void appWindow.isMaximized().then(setIsMaximized); + + const unlisten = appWindow.onResized(() => { + void appWindow.isMaximized().then(setIsMaximized); + }); + + return () => { + void unlisten.then((fn) => fn()); + }; + }, [isDecorated]); + + if (isDecorated || !isFullView) { + return null; + } + + return ( +
+
+
+ + + +
+
+ ); +}; diff --git a/new-ui/src/shared/components/WindowDecorations/style.scss b/new-ui/src/shared/components/WindowDecorations/style.scss new file mode 100644 index 000000000..d3cd9e6cc --- /dev/null +++ b/new-ui/src/shared/components/WindowDecorations/style.scss @@ -0,0 +1,75 @@ +#window-decorations { + --controls-display: none; + + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: flex-end; + width: 100%; + height: var(--window-decorations-height); + border-bottom: 1px solid var(--border-disabled); + box-sizing: border-box; + + &.macos { + > .window-drag { + margin-left: 100px; + } + } + + &.windows { + --controls-display: flex; + } + + > .window-drag { + content: ' '; + display: flex; + flex: 1 1 auto; + flex-flow: row; + min-width: 0; + height: 100%; + } + + > .window-controls { + display: flex; + flex-flow: row nowrap; + } + + button { + --bg: transparent; + --icon: var(--fg-white-80); + + border: 0; + background-color: var(--bg); + width: 46px; + height: 32px; + display: inline-flex; + flex-flow: row nowrap; + align-items: center; + justify-content: center; + cursor: pointer; + + @include animate(background-color); + + &:not(.close):hover { + --bg: var(--bg-white-10); + --icon: var(--fg-white-100); + } + + &.close:hover { + --bg: var(--bg-critical); + --icon: var(--fg-white-100); + } + + &.minimize svg path { + stroke: var(--icon); + } + + &.maximize svg rect { + stroke: var(--icon); + } + + &.close svg path { + fill: var(--icon); + } + } +} diff --git a/new-ui/src/shared/components/wizard/WizardPage/style.scss b/new-ui/src/shared/components/wizard/WizardPage/style.scss index a6b87db62..4d909fd7b 100644 --- a/new-ui/src/shared/components/wizard/WizardPage/style.scss +++ b/new-ui/src/shared/components/wizard/WizardPage/style.scss @@ -1,7 +1,7 @@ .wizard-page { --page-content-limit: 536px; - height: 100dvh; + height: calc(100dvh - var(--window-decorations-height)); overflow-y: auto; > .page-grid { diff --git a/new-ui/src/shared/layouts/FullPageLayout/style.scss b/new-ui/src/shared/layouts/FullPageLayout/style.scss index 9acccdcb9..59e4fd14f 100644 --- a/new-ui/src/shared/layouts/FullPageLayout/style.scss +++ b/new-ui/src/shared/layouts/FullPageLayout/style.scss @@ -2,7 +2,7 @@ display: grid; grid-template-columns: 52px 1fr; grid-template-rows: 50px 1fr; - height: 100dvh; + height: calc(100dvh - var(--window-decorations-height)); overflow: hidden; #window-header { diff --git a/new-ui/src/shared/scss/_shared_tokens.scss b/new-ui/src/shared/scss/_shared_tokens.scss index a46acbc63..70e43b415 100644 --- a/new-ui/src/shared/scss/_shared_tokens.scss +++ b/new-ui/src/shared/scss/_shared_tokens.scss @@ -21,6 +21,11 @@ $jetbrains: #{$font-fallback}; /* stylelint-disable value-keyword-case */ :root { + // sizings + // set by WindowDecorations component via effect on window metadata + // --window-decorations-height: 33px; + --window-header-height: 50px; + // font settings --font-family-title: #{$geist}; --font-family-body: #{$geist}; diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 853bdf681..0bc984fe0 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -43,6 +43,7 @@ "core:window:allow-set-cursor-position", "core:window:allow-set-ignore-cursor-events", "core:window:allow-start-dragging", + "core:window:allow-toggle-maximize", "core:webview:allow-print", "deep-link:default", "fs:default", diff --git a/src-tauri/src/window_manager/macos.rs b/src-tauri/src/window_manager/macos.rs index aebb24a3a..db919d8b8 100644 --- a/src-tauri/src/window_manager/macos.rs +++ b/src-tauri/src/window_manager/macos.rs @@ -1,4 +1,5 @@ use objc2_app_kit::{NSWindow, NSWindowButton, NSWindowStyleMask}; +use objc2_foundation::NSPoint; use tauri::{ AppHandle, LogicalPosition, LogicalSize, Manager, Monitor, Position, Runtime, WebviewWindow, }; @@ -19,18 +20,30 @@ pub(crate) fn enable_rounded_corners(window: &WebviewWindow) -> R ns_window.setStyleMask(style_mask); ns_window.setTitlebarAppearsTransparent(true); - // Hide the standard window buttons (close, minimize, zoom) - if let Some(close_button) = ns_window.standardWindowButton(NSWindowButton::CloseButton) - { - close_button.setHidden(true); - } - if let Some(miniaturize_button) = - ns_window.standardWindowButton(NSWindowButton::MiniaturizeButton) - { - miniaturize_button.setHidden(true); - } - if let Some(zoom_button) = ns_window.standardWindowButton(NSWindowButton::ZoomButton) { - zoom_button.setHidden(true); + // Position traffic light buttons: 20px from left, 12px from top to comply with figma design + let buttons = [ + ( + ns_window.standardWindowButton(NSWindowButton::CloseButton), + 20.0_f64, + ), + ( + ns_window.standardWindowButton(NSWindowButton::MiniaturizeButton), + 40.0_f64, + ), + ( + ns_window.standardWindowButton(NSWindowButton::ZoomButton), + 60.0_f64, + ), + ]; + for (button, x) in buttons { + if let Some(btn) = button { + let superview_height = btn + .superview() + .map(|sv| sv.frame().size.height) + .unwrap_or(28.0); + let y = superview_height - 12.0 - btn.frame().size.height; + btn.setFrameOrigin(NSPoint::new(x, y)); + } } }) .map_err(|err| err.to_string()) diff --git a/src-tauri/src/window_manager/mod.rs b/src-tauri/src/window_manager/mod.rs index d600f1c65..ae87129cd 100644 --- a/src-tauri/src/window_manager/mod.rs +++ b/src-tauri/src/window_manager/mod.rs @@ -70,7 +70,7 @@ impl WindowManager { .title(WINDOW_TITLE) .inner_size(FULL_VIEW_WINDOW_WIDTH, FULL_VIEW_WINDOW_HEIGHT) .min_inner_size(FULL_VIEW_WINDOW_WIDTH, FULL_VIEW_WINDOW_HEIGHT) - .decorations(true) + .decorations(cfg!(not(any(windows, target_os = "macos")))) .visible(false) .build() } diff --git a/src-tauri/tauri.local.conf.json b/src-tauri/tauri.local.conf.json index 0176062c1..b817d4607 100644 --- a/src-tauri/tauri.local.conf.json +++ b/src-tauri/tauri.local.conf.json @@ -1,5 +1,9 @@ { + "build": { + "beforeBundleCommand": null + }, "bundle": { + "targets": ["msi"], "windows": { "certificateThumbprint": null, "digestAlgorithm": null, From c87dc83b18cc60b58e3c4897594ecbc16fb5f72d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Wed, 1 Jul 2026 10:51:14 +0200 Subject: [PATCH 2/9] fix code submit on email totp modal forms --- .../ConnectModalMfaEmail.tsx | 32 +++++++++++------- .../ConnectModalMfaTotp.tsx | 33 +++++++++++-------- .../OverviewLocationCard.tsx | 1 + 3 files changed, 41 insertions(+), 25 deletions(-) diff --git a/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaEmail/ConnectModalMfaEmail.tsx b/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaEmail/ConnectModalMfaEmail.tsx index a2e0399b5..f17cf6ef9 100644 --- a/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaEmail/ConnectModalMfaEmail.tsx +++ b/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaEmail/ConnectModalMfaEmail.tsx @@ -36,17 +36,22 @@ export const ConnectModalMfaEmail = () => { const [emailCode, setEmailCode] = useState(null); const [error, setError] = useState(null); - const handleVerify = useCallback(() => { - if (!isPresent(emailCode)) { - setError('Enter code'); - return; - } - if (emailCode.length !== 6) { - setError('6 digits are required'); - return; - } - verifyCode(emailCode); - }, [emailCode, verifyCode]); + const handleVerify = useCallback( + (initCode?: string | null) => { + const codeToVerify = initCode ?? emailCode; + + if (!isPresent(codeToVerify)) { + setError('Enter code'); + return; + } + if (codeToVerify.length !== 6) { + setError('6 digits are required'); + return; + } + verifyCode(codeToVerify); + }, + [emailCode, verifyCode], + ); // biome-ignore lint/correctness/useExhaustiveDependencies: side effect of code input useEffect(() => { @@ -76,6 +81,9 @@ export const ConnectModalMfaEmail = () => { value={emailCode} onChange={setEmailCode} error={startError ?? error} + onSuccessPaste={(value) => { + handleVerify(value); + }} />
diff --git a/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaTotp/ConnectModalMfaTotp.tsx b/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaTotp/ConnectModalMfaTotp.tsx index 037d8a675..dba162116 100644 --- a/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaTotp/ConnectModalMfaTotp.tsx +++ b/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaTotp/ConnectModalMfaTotp.tsx @@ -36,17 +36,21 @@ export const ConnectModalMfaTotp = () => { const [totpCode, setTotpCode] = useState(null); const [error, setError] = useState(null); - const handleVerify = useCallback(() => { - if (!isPresent(totpCode)) { - setError('Enter code'); - return; - } - if (totpCode.replaceAll(' ', '').length !== 6) { - setError('6 digits are required'); - return; - } - verifyCode(totpCode); - }, [totpCode, verifyCode]); + const handleVerify = useCallback( + (initCode?: string | null) => { + const codeToVerify = initCode ?? totpCode; + if (!isPresent(codeToVerify)) { + setError('Enter code'); + return; + } + if (codeToVerify.replaceAll(' ', '').length !== 6) { + setError('6 digits are required'); + return; + } + verifyCode(codeToVerify); + }, + [totpCode, verifyCode], + ); // biome-ignore lint/correctness/useExhaustiveDependencies: side effect of code input useEffect(() => { @@ -74,8 +78,11 @@ export const ConnectModalMfaTotp = () => { setTotpCode(val)} error={startError ?? error} + onSuccessPaste={(value) => { + handleVerify(value); + }} />