Merge pull request #116 from LINUXexpert-org/docs-menu-link

Link the documentation from the profile menu
This commit is contained in:
LINUXexpert.org
2026-08-27 15:36:51 -07:00
committed by GitHub
3 changed files with 40 additions and 4 deletions
+5
View File
@@ -282,6 +282,11 @@ img { max-width: 100%; }
.menu-item { display: flex; align-items: center; gap: 10px; width: 100%; padding: 8px 10px; border-radius: var(--radius-sm); text-align: left; color: var(--fg); white-space: nowrap; }
.menu-item:hover, .menu-item.active { background: var(--bg-hover); }
.menu-item:disabled { opacity: .5; cursor: default; }
/* A menu entry that is a link still looks like a menu entry. The global rule
for `a` would otherwise colour and underline the one item that leaves the
app, which reads as a mistake rather than a distinction. */
a.menu-item { text-decoration: none; color: var(--fg); cursor: pointer; }
a.menu-item:hover { color: var(--fg); }
.menu-item.danger { color: var(--danger); }
.menu-item .menu-kbd { margin-left: auto; color: var(--fg-faint); font-size: .85em; }
.menu-item svg { color: var(--fg-muted); flex: 0 0 auto; }
+33 -3
View File
@@ -120,14 +120,44 @@ export interface MenuItemProps {
kbd?: string;
active?: boolean;
checked?: boolean;
/** Renders the item as a link. An external one gets a new tab. */
href?: string;
external?: boolean;
}
export function MenuItem({ icon, label, onClick, disabled, danger, kbd, active, checked }: MenuItemProps) {
return (
<button type="button" className={`menu-item ${danger ? "danger" : ""} ${active ? "active" : ""}`} onClick={onClick} disabled={disabled} role="menuitem">
export function MenuItem({ icon, label, onClick, disabled, danger, kbd, active, checked, href, external }: MenuItemProps) {
const inner = (
<>
{checked !== undefined ? <span style={{ width: 16, display: "inline-flex" }}>{checked ? "✓" : ""}</span> : icon}
<span className="grow truncate">{label}</span>
{kbd && <span className="menu-kbd">{kbd}</span>}
</>
);
const className = `menu-item ${danger ? "danger" : ""} ${active ? "active" : ""}`;
/*
* A real anchor when there is somewhere to go, rather than a button that
* calls window.open. The browser's own handling of a link comes with it --
* middle-click, a modifier-click, "open in new tab", the address on hover,
* copying it -- none of which a button offers however carefully it is
* scripted, and all of which someone expects from a menu entry that leaves
* the app.
*/
if (href) {
return (
<a
className={className}
href={href}
role="menuitem"
onClick={onClick}
{...(external ? { target: "_blank", rel: "noopener noreferrer" } : {})}
>
{inner}
</a>
);
}
return (
<button type="button" className={className} onClick={onClick} disabled={disabled} role="menuitem">
{inner}
</button>
);
}
+2 -1
View File
@@ -1,6 +1,6 @@
import { useEffect, useState, type ReactNode } from "react";
import { Link, useLocation } from "wouter";
import { Calendar, ChevronsUpDown, FolderOpen, HelpCircle, LogOut, Mail, Menu as MenuIcon, Moon, PenSquare, Plus, RefreshCw, Settings, Sun, Upload, Users } from "lucide-react";
import { BookOpen, Calendar, ChevronsUpDown, FolderOpen, HelpCircle, LogOut, Mail, Menu as MenuIcon, Moon, PenSquare, Plus, RefreshCw, Settings, Sun, Upload, Users } from "lucide-react";
import { useSession } from "@/store/session";
import { toggleTarget, useEffectiveTheme, useSettings } from "@/store/settings";
import { useMail } from "@/store/mail";
@@ -101,6 +101,7 @@ export function AppShell({ children }: { children: ReactNode }) {
</div>
</div>
<MenuSep />
<MenuItem icon={<BookOpen size={16} />} label="Documentation" href="https://docs.ihasmail.org" external />
<MenuItem icon={<Settings size={16} />} label="Settings" onClick={() => navigate("/settings")} />
<MenuItem icon={<RefreshCw size={16} />} label="Refresh" onClick={() => window.location.reload()} />
<MenuItem icon={<LogOut size={16} />} label="Sign out" onClick={() => void logout()} />