Merge pull request #116 from LINUXexpert-org/docs-menu-link
Link the documentation from the profile menu
This commit is contained in:
@@ -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 { 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:hover, .menu-item.active { background: var(--bg-hover); }
|
||||||
.menu-item:disabled { opacity: .5; cursor: default; }
|
.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.danger { color: var(--danger); }
|
||||||
.menu-item .menu-kbd { margin-left: auto; color: var(--fg-faint); font-size: .85em; }
|
.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; }
|
.menu-item svg { color: var(--fg-muted); flex: 0 0 auto; }
|
||||||
|
|||||||
+33
-3
@@ -120,14 +120,44 @@ export interface MenuItemProps {
|
|||||||
kbd?: string;
|
kbd?: string;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
checked?: 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) {
|
export function MenuItem({ icon, label, onClick, disabled, danger, kbd, active, checked, href, external }: MenuItemProps) {
|
||||||
return (
|
const inner = (
|
||||||
<button type="button" className={`menu-item ${danger ? "danger" : ""} ${active ? "active" : ""}`} onClick={onClick} disabled={disabled} role="menuitem">
|
<>
|
||||||
{checked !== undefined ? <span style={{ width: 16, display: "inline-flex" }}>{checked ? "✓" : ""}</span> : icon}
|
{checked !== undefined ? <span style={{ width: 16, display: "inline-flex" }}>{checked ? "✓" : ""}</span> : icon}
|
||||||
<span className="grow truncate">{label}</span>
|
<span className="grow truncate">{label}</span>
|
||||||
{kbd && <span className="menu-kbd">{kbd}</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>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState, type ReactNode } from "react";
|
import { useEffect, useState, type ReactNode } from "react";
|
||||||
import { Link, useLocation } from "wouter";
|
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 { useSession } from "@/store/session";
|
||||||
import { toggleTarget, useEffectiveTheme, useSettings } from "@/store/settings";
|
import { toggleTarget, useEffectiveTheme, useSettings } from "@/store/settings";
|
||||||
import { useMail } from "@/store/mail";
|
import { useMail } from "@/store/mail";
|
||||||
@@ -101,6 +101,7 @@ export function AppShell({ children }: { children: ReactNode }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MenuSep />
|
<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={<Settings size={16} />} label="Settings" onClick={() => navigate("/settings")} />
|
||||||
<MenuItem icon={<RefreshCw size={16} />} label="Refresh" onClick={() => window.location.reload()} />
|
<MenuItem icon={<RefreshCw size={16} />} label="Refresh" onClick={() => window.location.reload()} />
|
||||||
<MenuItem icon={<LogOut size={16} />} label="Sign out" onClick={() => void logout()} />
|
<MenuItem icon={<LogOut size={16} />} label="Sign out" onClick={() => void logout()} />
|
||||||
|
|||||||
Reference in New Issue
Block a user