136 lines
3.7 KiB
TypeScript
136 lines
3.7 KiB
TypeScript
|
|
import React, { MouseEvent, ReactNode } from 'react';
|
||
|
|
import { useAtomValue } from 'jotai';
|
||
|
|
import { Text } from 'folds';
|
||
|
|
import { customWindowChromeAtom } from '../../state/customWindowChrome';
|
||
|
|
import { invokeTauri, isTauri } from '../../hooks/useTauri';
|
||
|
|
import * as css from './TitleBar.css';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Detect macOS from the web side (no `tauri-plugin-os` dependency). We only need
|
||
|
|
* a coarse "is this a Mac" signal to decide which side the window controls sit
|
||
|
|
* on, so the UA/platform sniff is sufficient and stays cross-platform.
|
||
|
|
*/
|
||
|
|
const isMacOS = (): boolean => {
|
||
|
|
const platform =
|
||
|
|
(
|
||
|
|
navigator as unknown as {
|
||
|
|
userAgentData?: { platform?: string };
|
||
|
|
}
|
||
|
|
).userAgentData?.platform ??
|
||
|
|
navigator.platform ??
|
||
|
|
navigator.userAgent;
|
||
|
|
return /mac/i.test(platform);
|
||
|
|
};
|
||
|
|
|
||
|
|
const MIN_GLYPH = (
|
||
|
|
<svg width="10" height="10" viewBox="0 0 10 10" aria-hidden>
|
||
|
|
<rect x="1" y="4.5" width="8" height="1" fill="currentColor" />
|
||
|
|
</svg>
|
||
|
|
);
|
||
|
|
|
||
|
|
const MAX_GLYPH = (
|
||
|
|
<svg width="10" height="10" viewBox="0 0 10 10" aria-hidden>
|
||
|
|
<rect x="1" y="1" width="8" height="8" fill="none" stroke="currentColor" strokeWidth="1" />
|
||
|
|
</svg>
|
||
|
|
);
|
||
|
|
|
||
|
|
const CLOSE_GLYPH = (
|
||
|
|
<svg width="10" height="10" viewBox="0 0 10 10" aria-hidden>
|
||
|
|
<path d="M1 1 L9 9 M9 1 L1 9" stroke="currentColor" strokeWidth="1" fill="none" />
|
||
|
|
</svg>
|
||
|
|
);
|
||
|
|
|
||
|
|
type ControlButtonProps = {
|
||
|
|
label: string;
|
||
|
|
glyph: ReactNode;
|
||
|
|
onClick: () => void;
|
||
|
|
close?: boolean;
|
||
|
|
};
|
||
|
|
|
||
|
|
function ControlButton({ label, glyph, onClick, close }: ControlButtonProps) {
|
||
|
|
return (
|
||
|
|
<button
|
||
|
|
type="button"
|
||
|
|
aria-label={label}
|
||
|
|
title={label}
|
||
|
|
onClick={onClick}
|
||
|
|
className={`${css.ControlButton}${close ? ` ${css.ControlButtonClose}` : ''}`}
|
||
|
|
>
|
||
|
|
{glyph}
|
||
|
|
</button>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* P5-47 — TDS Custom Window Chrome titlebar.
|
||
|
|
*
|
||
|
|
* Renders `null` unless we're inside Tauri **and** the user opted into custom
|
||
|
|
* window chrome. Otherwise it draws a thin (~32px) folds/TDS-styled titlebar: a
|
||
|
|
* draggable region (`data-tauri-drag-region`) with the app brand, plus
|
||
|
|
* minimize / maximize / close controls that call the native window commands.
|
||
|
|
*
|
||
|
|
* OS-aware: Windows/Linux put the controls on the right; macOS mirrors them to
|
||
|
|
* the left (the native traffic-light position) since decorations — and thus the
|
||
|
|
* real traffic lights — are stripped while custom chrome is on.
|
||
|
|
*/
|
||
|
|
export function TitleBar() {
|
||
|
|
const enabled = useAtomValue(customWindowChromeAtom);
|
||
|
|
|
||
|
|
if (!isTauri() || !enabled) return null;
|
||
|
|
|
||
|
|
const mac = isMacOS();
|
||
|
|
|
||
|
|
const handleDoubleClick = (evt: MouseEvent<HTMLDivElement>): void => {
|
||
|
|
// Only the drag surface itself toggles maximize, not the brand/children.
|
||
|
|
if (evt.target !== evt.currentTarget) return;
|
||
|
|
invokeTauri('window_toggle_maximize');
|
||
|
|
};
|
||
|
|
|
||
|
|
const controls = (
|
||
|
|
<div className={css.Controls}>
|
||
|
|
<ControlButton
|
||
|
|
label="Minimize"
|
||
|
|
glyph={MIN_GLYPH}
|
||
|
|
onClick={() => invokeTauri('window_minimize')}
|
||
|
|
/>
|
||
|
|
<ControlButton
|
||
|
|
label="Maximize"
|
||
|
|
glyph={MAX_GLYPH}
|
||
|
|
onClick={() => invokeTauri('window_toggle_maximize')}
|
||
|
|
/>
|
||
|
|
<ControlButton
|
||
|
|
label="Close"
|
||
|
|
glyph={CLOSE_GLYPH}
|
||
|
|
onClick={() => invokeTauri('window_close')}
|
||
|
|
close
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
|
||
|
|
const dragRegion = (
|
||
|
|
<div className={css.DragRegion} data-tauri-drag-region onDoubleClick={handleDoubleClick}>
|
||
|
|
<span className={css.Brand}>
|
||
|
|
<Text as="span" size="T200" truncate>
|
||
|
|
Lotus Chat
|
||
|
|
</Text>
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<header className={css.TitleBar}>
|
||
|
|
{mac ? (
|
||
|
|
<>
|
||
|
|
{controls}
|
||
|
|
{dragRegion}
|
||
|
|
</>
|
||
|
|
) : (
|
||
|
|
<>
|
||
|
|
{dragRegion}
|
||
|
|
{controls}
|
||
|
|
</>
|
||
|
|
)}
|
||
|
|
</header>
|
||
|
|
);
|
||
|
|
}
|