Drop the title bar; drag the window by the nav
The page now runs to the very top of the window: titleBarStyle Overlay gives the full size content view, so there is no reserved band above the apps and the nav reaches the top edge with the traffic lights floating over it. That costs the old drag strip. A native webview swallows mouse events, so the shell cannot offer a drag region anywhere over a page — once the page starts at y=0 the only surface left is the nav. So the nav becomes the handle, all of it, at either width. Every container carries the attribute and only the buttons do not, which keeps them clickable: a drag region is decided by the element under the cursor. The scrolling app list is included, and loses nothing by it — mousedown is all a drag region takes, never the wheel or a trackpad swipe. Verified by dragging from bare points in both the collapsed rail and the expanded panel: the window follows the delta exactly, and clicking an app still switches to it rather than moving the window.
This commit is contained in:
@@ -19,7 +19,8 @@
|
||||
"minWidth": 900,
|
||||
"minHeight": 600,
|
||||
"center": true,
|
||||
"hiddenTitle": true
|
||||
"hiddenTitle": true,
|
||||
"titleBarStyle": "Overlay"
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
|
||||
+6
-2
@@ -265,9 +265,13 @@ export default function App() {
|
||||
/>
|
||||
|
||||
{/* The hole an app's native webview is positioned into. It stays empty
|
||||
on purpose — anything drawn here would be painted over. */}
|
||||
on purpose — anything drawn here would be painted over.
|
||||
|
||||
It runs to the very top of the window: there is no title bar to sit
|
||||
under. Nothing draggable can go here either, for the same reason the
|
||||
hole is empty — the webview would swallow it. Dragging lives in the
|
||||
nav, which is the one column the shell still owns. */}
|
||||
<div className="flex min-w-0 flex-1 flex-col">
|
||||
<div data-tauri-drag-region className="shrink-0" style={{ height: chrome }} />
|
||||
<div
|
||||
ref={stageRef}
|
||||
className="relative min-h-0 flex-1 overflow-hidden bg-slate-100 dark:bg-slate-950"
|
||||
|
||||
+43
-16
@@ -127,34 +127,52 @@ export default function Nav({
|
||||
};
|
||||
|
||||
return (
|
||||
<aside className={`${shell} items-center`} style={{ width: rail }}>
|
||||
<aside
|
||||
data-tauri-drag-region
|
||||
className={`${shell} items-center`}
|
||||
style={{ width: rail }}
|
||||
>
|
||||
{/* With no title bar left, the nav is the only surface the shell
|
||||
still owns, so the whole of it drags the window — every container
|
||||
here carries the attribute, and only the buttons do not. Costs
|
||||
nothing on the scrolling list: a drag region intercepts mousedown,
|
||||
never the wheel or a trackpad swipe. */}
|
||||
<div data-tauri-drag-region className="w-full shrink-0" style={{ height: chrome }} />
|
||||
{/* Only the expander survives the rail's header. Back and forward are
|
||||
a two-finger swipe and reload is ⌘R, so a toolbar here would be
|
||||
clutter standing in for something nobody asked for. */}
|
||||
<div className="flex w-full flex-col items-center border-b border-slate-200 py-2.5 dark:border-slate-800">
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className="flex w-full flex-col items-center border-b border-slate-200 py-2.5 dark:border-slate-800"
|
||||
>
|
||||
<button onClick={onToggleCollapse} title="Expand" className={ICON_CHROME}>
|
||||
<Collapse open={false} />
|
||||
</button>
|
||||
</div>
|
||||
<nav className="flex min-h-0 flex-1 flex-col items-center gap-1 overflow-y-auto py-3">
|
||||
<nav
|
||||
data-tauri-drag-region
|
||||
className="flex min-h-0 flex-1 flex-col items-center gap-1 overflow-y-auto py-3"
|
||||
>
|
||||
{ungrouped.map(railBtn)}
|
||||
{ungrouped.length > 0 && groups.length > 0 && (
|
||||
<span className="my-1 h-px w-7 bg-slate-200 dark:bg-slate-800" />
|
||||
<span data-tauri-drag-region className="my-1 h-px w-7 bg-slate-200 dark:bg-slate-800" />
|
||||
)}
|
||||
{groups.map((g, i) => {
|
||||
const apps = inGroup(g.id);
|
||||
if (apps.length === 0) return null;
|
||||
return (
|
||||
<div key={g.id} className="flex flex-col items-center gap-1">
|
||||
{i > 0 && <span className="my-1 h-px w-7 bg-slate-200 dark:bg-slate-800" />}
|
||||
<div key={g.id} data-tauri-drag-region className="flex flex-col items-center gap-1">
|
||||
{i > 0 && <span data-tauri-drag-region className="my-1 h-px w-7 bg-slate-200 dark:bg-slate-800" />}
|
||||
{apps.map(railBtn)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
<Downloads items={downloads} collapsed onDismiss={onDismissDownload} />
|
||||
<div className="flex w-full flex-col items-center border-t border-slate-200 py-2 dark:border-slate-800">
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className="flex w-full flex-col items-center border-t border-slate-200 py-2 dark:border-slate-800"
|
||||
>
|
||||
{settingsButton}
|
||||
</div>
|
||||
</aside>
|
||||
@@ -169,7 +187,7 @@ export default function Nav({
|
||||
const active = "bg-slate-900 text-white dark:bg-white dark:text-slate-900";
|
||||
|
||||
const appRow = (app: WorkApp) => (
|
||||
<li key={app.id}>
|
||||
<li key={app.id} data-tauri-drag-region>
|
||||
<button
|
||||
onClick={() => onSelect(app.id)}
|
||||
title={app.url}
|
||||
@@ -183,9 +201,12 @@ export default function Nav({
|
||||
);
|
||||
|
||||
return (
|
||||
<aside className={shell} style={{ width: PANEL }}>
|
||||
<aside data-tauri-drag-region className={shell} style={{ width: PANEL }}>
|
||||
<div data-tauri-drag-region className="shrink-0" style={{ height: chrome }} />
|
||||
<header className="flex items-center gap-0.5 border-b border-slate-200 px-1.5 py-2.5 dark:border-slate-800">
|
||||
<header
|
||||
data-tauri-drag-region
|
||||
className="flex items-center gap-0.5 border-b border-slate-200 px-1.5 py-2.5 dark:border-slate-800"
|
||||
>
|
||||
{controls}
|
||||
<button
|
||||
onClick={onToggleCollapse}
|
||||
@@ -196,13 +217,13 @@ export default function Nav({
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<nav className="min-h-0 flex-1 overflow-y-auto px-2 py-3">
|
||||
{ungrouped.length > 0 && <ul className="space-y-0.5">{ungrouped.map(appRow)}</ul>}
|
||||
<nav data-tauri-drag-region className="min-h-0 flex-1 overflow-y-auto px-2 py-3">
|
||||
{ungrouped.length > 0 && <ul data-tauri-drag-region className="space-y-0.5">{ungrouped.map(appRow)}</ul>}
|
||||
|
||||
{groups.map((g) => {
|
||||
const apps = inGroup(g.id);
|
||||
return (
|
||||
<section key={g.id} className="pt-3 first:pt-0">
|
||||
<section key={g.id} data-tauri-drag-region className="pt-3 first:pt-0">
|
||||
<button
|
||||
onClick={() => onToggleGroup(g)}
|
||||
className={`${GROUP_LABEL} flex w-full cursor-pointer items-center gap-1 px-2 pb-1
|
||||
@@ -218,13 +239,16 @@ export default function Nav({
|
||||
<span className="truncate">{g.name}</span>
|
||||
<span className="ml-auto font-mono text-[10px] opacity-60">{apps.length}</span>
|
||||
</button>
|
||||
{!g.collapsed && <ul className="space-y-0.5">{apps.map(appRow)}</ul>}
|
||||
{!g.collapsed && <ul data-tauri-drag-region className="space-y-0.5">{apps.map(appRow)}</ul>}
|
||||
</section>
|
||||
);
|
||||
})}
|
||||
|
||||
{config.apps.length === 0 && (
|
||||
<p className="px-2 py-3 text-[11px] leading-snug text-slate-500 dark:text-slate-400">
|
||||
<p
|
||||
data-tauri-drag-region
|
||||
className="px-2 py-3 text-[11px] leading-snug text-slate-500 dark:text-slate-400"
|
||||
>
|
||||
No apps yet. Open Settings to add the first one.
|
||||
</p>
|
||||
)}
|
||||
@@ -232,7 +256,10 @@ export default function Nav({
|
||||
|
||||
<Downloads items={downloads} collapsed={false} onDismiss={onDismissDownload} />
|
||||
|
||||
<footer className="flex items-center gap-1 border-t border-slate-200 px-1.5 py-2 dark:border-slate-800">
|
||||
<footer
|
||||
data-tauri-drag-region
|
||||
className="flex items-center gap-1 border-t border-slate-200 px-1.5 py-2 dark:border-slate-800"
|
||||
>
|
||||
{settingsButton}
|
||||
</footer>
|
||||
</aside>
|
||||
|
||||
Reference in New Issue
Block a user