fix(ui): portal tooltips, opaque glass, settings rail+stable height, notif test, channel help

- Tooltip: render in a portal with fixed positioning + edge-flip so hints are never
  clipped by overflow/stacking ancestors (fixes mispositioned/hidden bubbles app-wide).
- Glass: raise opacity so overlay menus/panels stay readable over content.
- SettingsPanel: vertical tab rail (no wrapping/jumping), content grid-stacked so the
  panel sizes to the tallest tab (stable height) and floats to its content height.
- Notifications: the test toast is now a normal auto-dismissing toast (with countdown
  bar) that also plays the sound via a new force-sound flag.
- Channel manager: explain priority/tags/hide and what 'Sync subscriptions' does;
  add a 'Channel priority' feed sort so priority is actually meaningful.
This commit is contained in:
npeter83 2026-06-11 21:30:25 +02:00
parent f8d2a11226
commit 91fd008519
7 changed files with 160 additions and 79 deletions

View file

@ -60,7 +60,7 @@ export default function SettingsPanel({
onClick={close}
/>
<div
className={`glass relative w-[min(94vw,460px)] h-full rounded-l-2xl flex flex-col ${
className={`glass relative self-start m-3 w-[min(94vw,520px)] max-h-[calc(100vh-1.5rem)] rounded-2xl overflow-hidden flex flex-col ${
closing
? "animate-[panelOut_0.19s_ease-in_forwards]"
: "animate-[panelIn_0.26s_cubic-bezier(0.22,1,0.36,1)]"
@ -76,34 +76,47 @@ export default function SettingsPanel({
</button>
</div>
{/* Wrapping pill tabs — no horizontal scrollbar, prominent active state. */}
<div className="flex flex-wrap gap-1.5 px-3 py-3 border-b border-border/60 shrink-0">
{TABS.map((t) => {
const active = tab === t.id;
return (
<button
<div className="flex flex-1 min-h-0">
{/* Vertical tab rail — never wraps; active is a clear accent pill. */}
<nav className="w-32 sm:w-36 shrink-0 border-r border-border/60 p-2 flex flex-col gap-1">
{TABS.map((t) => {
const active = tab === t.id;
return (
<button
key={t.id}
onClick={() => setTab(t.id)}
className={`flex items-center gap-2 px-3 py-2 rounded-lg text-sm text-left transition ${
active
? "bg-accent text-accent-fg font-medium shadow-md shadow-accent/20"
: "text-muted hover:text-fg hover:bg-card/60"
}`}
>
<t.icon className="w-4 h-4 shrink-0" />
{t.label}
</button>
);
})}
</nav>
{/* All tabs stacked in one grid cell so the panel sizes to the tallest tab
(stable height, no jump on switch); the active one is shown on top. */}
<div className="grid flex-1 overflow-y-auto">
{TABS.map((t) => (
<div
key={t.id}
onClick={() => setTab(t.id)}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-full text-sm transition ${
active
? "bg-accent text-accent-fg shadow-md shadow-accent/20 font-medium"
: "text-muted hover:text-fg hover:bg-card/60"
className={`[grid-area:1/1] p-4 ${
tab === t.id ? "" : "invisible pointer-events-none"
}`}
>
<t.icon className="w-4 h-4" />
{t.label}
</button>
);
})}
</div>
<div className="flex-1 overflow-y-auto p-4">
{tab === "appearance" && (
<Appearance theme={theme} setTheme={setTheme} view={view} setView={setView} />
)}
{tab === "notifications" && <Notifications />}
{tab === "sync" && <Sync me={me} />}
{tab === "account" && <Account me={me} />}
{t.id === "appearance" && (
<Appearance theme={theme} setTheme={setTheme} view={view} setView={setView} />
)}
{t.id === "notifications" && <Notifications />}
{t.id === "sync" && <Sync me={me} />}
{t.id === "account" && <Account me={me} />}
</div>
))}
</div>
</div>
</div>
</div>
@ -294,7 +307,7 @@ function Notifications() {
level: "info",
title: "Test notification",
message: "This is what a notification looks like.",
requiresInteraction: true,
sound: true,
})
}
className="mt-2 text-sm text-accent hover:underline"