Skip to content

Commit 753bb61

Browse files
feat: add macOS-style traffic light buttons for Linux (#1988)
* feat: add macOS-style traffic light buttons for Linux On Linux, window decorations are removed, leaving no way to close/minimize/maximize windows. This adds macOS-style traffic light buttons to the settings window header for Linux users. Changes: - Add usePlatform hook for platform detection using @tauri-apps/plugin-os - Add TrafficLights component with close/minimize/maximize buttons - Integrate TrafficLights into settings layout for Linux only Co-Authored-By: yujonglee <yujonglee.dev@gmail.com> * fix: enable minimize/maximize for Settings window and add traffic lights to main window Co-Authored-By: yujonglee <yujonglee.dev@gmail.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent f7c8d66 commit 753bb61

File tree

5 files changed

+68
-1
lines changed

5 files changed

+68
-1
lines changed

apps/desktop/src/components/main/body/index.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ import { Button } from "@hypr/ui/components/ui/button";
1313
import { cn } from "@hypr/utils";
1414

1515
import { useShell } from "../../../contexts/shell";
16+
import { useIsLinux } from "../../../hooks/usePlatform";
1617
import {
1718
type Tab,
1819
uniqueIdfromTab,
1920
useTabs,
2021
} from "../../../store/zustand/tabs";
2122
import { ChatFloatingButton } from "../../chat";
23+
import { TrafficLights } from "../../window/traffic-lights";
2224
import { useNewNote } from "../shared";
2325
import { TabContentContact, TabItemContact } from "./contacts";
2426
import { TabContentEmpty, TabItemEmpty } from "./empty";
@@ -58,6 +60,7 @@ export function Body() {
5860

5961
function Header({ tabs }: { tabs: Tab[] }) {
6062
const { leftsidebar } = useShell();
63+
const isLinux = useIsLinux();
6164
const {
6265
select,
6366
close,
@@ -93,8 +96,10 @@ function Header({ tabs }: { tabs: Tab[] }) {
9396
className={cn([
9497
"w-full h-9 flex items-center",
9598
!leftsidebar.expanded && "pl-[72px]",
99+
isLinux && "pl-3",
96100
])}
97101
>
102+
{isLinux && <TrafficLights className="mr-2" />}
98103
{!leftsidebar.expanded && (
99104
<Button
100105
size="icon"
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
2+
3+
import { cn } from "@hypr/utils";
4+
5+
export function TrafficLights({ className }: { className?: string }) {
6+
const win = getCurrentWebviewWindow();
7+
8+
const onClose = () => win.close();
9+
const onMinimize = () => win.minimize();
10+
const onMaximize = () => win.toggleMaximize();
11+
12+
return (
13+
<div className={cn(["flex gap-2 items-center", className])}>
14+
<button
15+
type="button"
16+
data-tauri-drag-region="false"
17+
onClick={onClose}
18+
className="h-3 w-3 rounded-full bg-[#ff5f57] border border-black/10 hover:brightness-90 transition-all"
19+
/>
20+
<button
21+
type="button"
22+
data-tauri-drag-region="false"
23+
onClick={onMinimize}
24+
className="h-3 w-3 rounded-full bg-[#ffbd2e] border border-black/10 hover:brightness-90 transition-all"
25+
/>
26+
<button
27+
type="button"
28+
data-tauri-drag-region="false"
29+
onClick={onMaximize}
30+
className="h-3 w-3 rounded-full bg-[#28c840] border border-black/10 hover:brightness-90 transition-all"
31+
/>
32+
</div>
33+
);
34+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { platform, type Platform } from "@tauri-apps/plugin-os";
2+
3+
export type { Platform };
4+
5+
export function usePlatform(): Platform {
6+
return platform();
7+
}
8+
9+
export function useIsLinux(): boolean {
10+
return usePlatform() === "linux";
11+
}
12+
13+
export function useIsMacos(): boolean {
14+
return usePlatform() === "macos";
15+
}
16+
17+
export function useIsWindows(): boolean {
18+
return usePlatform() === "windows";
19+
}

apps/desktop/src/routes/app/settings/_layout.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import { Button } from "@hypr/ui/components/ui/button";
2323
import { cn } from "@hypr/utils";
2424

2525
import { useTemplateNavigation } from "../../../components/settings/template/utils";
26+
import { TrafficLights } from "../../../components/window/traffic-lights";
27+
import { useIsLinux } from "../../../hooks/usePlatform";
2628
import * as main from "../../../store/tinybase/main";
2729

2830
const TAB_KEYS = [
@@ -166,6 +168,7 @@ function Group({
166168
includeTrafficLight?: boolean;
167169
}) {
168170
const navigate = Route.useNavigate();
171+
const isLinux = useIsLinux();
169172

170173
const handleTabClick = async (tab: TabKey) => {
171174
if (tab === "feedback") {
@@ -184,7 +187,11 @@ function Group({
184187
expandHeight && "flex-1",
185188
])}
186189
>
187-
{includeTrafficLight && <div data-tauri-drag-region className="h-9" />}
190+
{includeTrafficLight && (
191+
<div data-tauri-drag-region className="h-9 flex items-center px-3">
192+
{isLinux && <TrafficLights />}
193+
</div>
194+
)}
188195
{tabs.map((tab) => {
189196
const tabInfo = info(tab);
190197
const Icon = tabInfo.icon;

plugins/windows/src/window/v1.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ impl WindowImpl for AppWindow {
132132
let window = self
133133
.window_builder(app, "/app/settings")
134134
.resizable(true)
135+
.minimizable(true)
136+
.maximizable(true)
135137
.min_inner_size(800.0, 600.0)
136138
.build()?;
137139

0 commit comments

Comments
 (0)