feat(design): the button families on the decided ladder, first half of the sweep (E28 S3b1)
Button is rebuilt to A1/B1/B3: explicit heights (xs 24 / sm 28 / md 32, lg 40 for a hero CTA), r8 (a pill on media), state layers instead of opacity and bg-card hovers, a `pressed` toggle, `overlay`, `media` and `link-muted` variants. The icon-only sizes move to the new IconButton (28 / 32, 48 only for the Play on a picture; a required label). Pressable (a card, row or tile that is the control) and DragHandle join them. The state-* utilities now stand down on a disabled, busy or pressed control, held by theme.contract.test.ts. Swept onto them: the Button icon-size callers and 16 files (Plex playlist, Plex detail, the YT player modal chrome, Welcome, Plex and video cards with AddToPlaylist and DownloadButton, the video editor, Feed, personal views, Scheduler, Users, the nav rail, Plex detail parts, Settings): 268 raw buttons are 153. Fixed on the way: FocusSafeButton overwrote a caller's aria-disabled (the "unavailable, here is why" state; adminInvites caught it), a season toggle and a Plex settings switch had no name/state, and the video card's overlay follows the C5 card (Resume pill, From the start pill, a round Play).
This commit is contained in:
@@ -14,12 +14,13 @@ import type { Page } from "../lib/urlState";
|
||||
import { moduleDef, moduleLabelKey, moduleOrder, SYSTEM_PAGES } from "../lib/modules";
|
||||
import { useScrollFade } from "../lib/useScrollFade";
|
||||
import { type LangCode } from "../i18n";
|
||||
import { Button } from "./ui/Button";
|
||||
import { IconButton } from "./ui/IconButton";
|
||||
import AvatarImg from "./ui/Avatar";
|
||||
import Tooltip from "./ui/Tooltip";
|
||||
import LanguageSwitcher from "./LanguageSwitcher";
|
||||
import { PopoverSurface } from "./ui/AnchoredPopover";
|
||||
import { useAnchoredPopover } from "./ui/anchoredPopoverHooks";
|
||||
import { Pressable } from "./ui/Pressable";
|
||||
|
||||
// Primary app navigation: a thin icon-only left rail. The modules used to hide under the avatar
|
||||
// dropdown; they now live here, each revealing its name in a hover/focus flyout label. Account
|
||||
@@ -170,7 +171,7 @@ export default function NavSidebar({
|
||||
return (
|
||||
// The name rides in a right-side flyout on hover (rest-to-show), so the rail stays icon-only.
|
||||
<Tooltip key={p} hint={label} side="right" always openDelay={350}>
|
||||
<button
|
||||
<Pressable
|
||||
data-testid={`nav-${p}`}
|
||||
onClick={() => setPage(p)}
|
||||
aria-label={label}
|
||||
@@ -191,7 +192,7 @@ export default function NavSidebar({
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
</Pressable>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -220,25 +221,25 @@ export default function NavSidebar({
|
||||
<div className="mt-2 pt-2 border-t border-border flex flex-col gap-1">
|
||||
<div className="flex flex-col items-center justify-center gap-2 pb-2 mb-1 border-b border-border">
|
||||
<LanguageSwitcher variant="rail" value={language} onChange={onChangeLanguage} />
|
||||
<Button
|
||||
<IconButton
|
||||
label={t("header.account.about")}
|
||||
size="md"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={onOpenAbout}
|
||||
title={t("header.account.about")}
|
||||
aria-label={t("header.account.about")}
|
||||
icon={<Info className="w-5 h-5" />}
|
||||
/>
|
||||
<Button
|
||||
>
|
||||
<Info className="w-5 h-5" />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
label={t("header.account.signOut")}
|
||||
size="md"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={logout}
|
||||
title={t("header.account.signOut")}
|
||||
aria-label={t("header.account.signOut")}
|
||||
icon={<LogOut className="w-5 h-5" />}
|
||||
/>
|
||||
>
|
||||
<LogOut className="w-5 h-5" />
|
||||
</IconButton>
|
||||
</div>
|
||||
<Tooltip hint={t("header.account.settings")} side="right" always openDelay={350}>
|
||||
<button
|
||||
<Pressable
|
||||
data-testid="nav-settings"
|
||||
onClick={() => setPage("settings")}
|
||||
aria-label={t("header.account.settings")}
|
||||
@@ -246,11 +247,11 @@ export default function NavSidebar({
|
||||
className={`${rowBase} justify-center px-0 is-current`}
|
||||
>
|
||||
<Settings className="w-4.5 h-4.5 shrink-0" />
|
||||
</button>
|
||||
</Pressable>
|
||||
</Tooltip>
|
||||
|
||||
<div>
|
||||
<button
|
||||
<Pressable
|
||||
ref={acctPop.refs.setReference}
|
||||
{...acctPop.getReferenceProps()}
|
||||
title={`${name} · ${roleLabel}`}
|
||||
@@ -264,7 +265,7 @@ export default function NavSidebar({
|
||||
className="w-5.5 h-5.5 rounded-full text-3xs"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</Pressable>
|
||||
|
||||
<PopoverSurface
|
||||
popover={acctPop}
|
||||
@@ -305,7 +306,7 @@ export default function NavSidebar({
|
||||
{otherAccounts.length > 0 && (
|
||||
<div className="mt-2 pt-2 border-t border-border flex flex-col gap-0.5">
|
||||
{otherAccounts.map((a) => (
|
||||
<button
|
||||
<Pressable
|
||||
key={a.id}
|
||||
onClick={() => switchTo(a.id)}
|
||||
title={t("header.account.switchTo", { name: a.display_name ?? a.email })}
|
||||
@@ -316,18 +317,18 @@ export default function NavSidebar({
|
||||
fallback={a.display_name ?? a.email}
|
||||
className="w-6 h-6 rounded-full text-3xs shrink-0"
|
||||
/>
|
||||
<span className="min-w-0 text-left">
|
||||
<span className="min-w-0">
|
||||
<span className="block text-[0.8125rem] text-fg truncate">
|
||||
{displayName(a)}
|
||||
</span>
|
||||
<span className="block text-2xs text-muted truncate">{a.email}</span>
|
||||
</span>
|
||||
</button>
|
||||
</Pressable>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-2 pt-2 border-t border-border flex flex-col">
|
||||
<button
|
||||
<Pressable
|
||||
onClick={() => {
|
||||
// Adding an account switches THIS tab to it: drop the tab's pin so that on
|
||||
// return from Google it adopts the freshly-added account (the new default)
|
||||
@@ -339,14 +340,14 @@ export default function NavSidebar({
|
||||
>
|
||||
<UserPlus className="w-4 h-4" />
|
||||
{t("header.account.addAccount")}
|
||||
</button>
|
||||
<button
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onClick={logout}
|
||||
className="selectable w-full flex items-center gap-2 text-sm px-2 py-2 rounded-lg text-muted hover:text-fg hover:bg-card transition"
|
||||
>
|
||||
<LogOut className="w-4 h-4" />
|
||||
{t("header.account.signOut")}
|
||||
</button>
|
||||
</Pressable>
|
||||
</div>
|
||||
</PopoverSurface>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user