feat(web): mobile dashboard UX polish (#28127)

* feat(web): mobile dashboard UX polish

Bottom sheets for sidebar theme/language pickers on narrow viewports with
enter/exit animation and drag-to-close; inline header badges beside titles;
bottom padding on the route outlet for scroll clearance; profiles loading uses a
unicode braille spinner; align profile/cron card actions to the top; viewport-fit
cover and supporting layout tweaks across dashboard pages.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix Nix web npm hash and mobile sheet accessibility.

Align fetchNpmDeps in nix/web.nix with web/package-lock.json for CI. Improve BottomPickSheet backdrop labeling, avoid aria-hidden on the dialog during exit animation, and wire theme/language sheets with listbox semantics and localized dismiss labels.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Austin Pickett
2026-05-18 15:20:31 -04:00
committed by GitHub
co-authored by Cursor
parent 52e3bfc2f4
commit 6fa1701bd3
24 changed files with 779 additions and 295 deletions
+8 -13
View File
@@ -205,7 +205,7 @@ export default function SkillsPage() {
<div className="relative w-full min-w-0 sm:max-w-xs">
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
<Input
className="h-8 pl-8 pr-7 text-xs"
className="h-8 rounded-none pl-8 pr-7 text-xs"
placeholder={t.common.search}
value={search}
onChange={(e) => setSearch(e.target.value)}
@@ -256,12 +256,7 @@ export default function SkillsPage() {
<div className="flex flex-col sm:flex-row sm:items-start gap-4">
<aside aria-label={t.skills.title} className="sm:w-56 sm:shrink-0">
<div className="sm:sticky sm:top-0">
<div
className={`
flex flex-col
border border-border bg-muted/20
`}
>
<div className="flex flex-col rounded-none border border-border bg-muted/20">
<div className="hidden sm:flex items-center gap-2 px-3 py-2 border-b border-border">
<Filter className="h-3 w-3 text-muted-foreground" />
<span className="font-mondwest text-[0.65rem] tracking-[0.12em] uppercase text-muted-foreground">
@@ -309,7 +304,7 @@ export default function SkillsPage() {
onClick={() =>
setActiveCategory(isActive ? null : key)
}
className="rounded-sm px-2 py-1 text-[11px]"
className="rounded-none px-2 py-1 text-[11px]"
>
<span className="flex-1 truncate">{name}</span>
<span
@@ -333,7 +328,7 @@ export default function SkillsPage() {
<div className="flex-1 min-w-0">
{isSearching ? (
<Card>
<Card className="rounded-none">
<CardHeader className="py-3 px-4">
<div className="flex items-center justify-between">
<CardTitle className="text-sm flex items-center gap-2">
@@ -372,7 +367,7 @@ export default function SkillsPage() {
</Card>
) : view === "skills" ? (
/* Skills list */
<Card>
<Card className="rounded-none">
<CardHeader className="py-3 px-4">
<div className="flex items-center justify-between">
<CardTitle className="text-sm flex items-center gap-2">
@@ -417,7 +412,7 @@ export default function SkillsPage() {
/* Toolsets grid */
<>
{filteredToolsets.length === 0 ? (
<Card>
<Card className="rounded-none">
<CardContent className="py-8 text-center text-sm text-muted-foreground">
{t.skills.noToolsetsMatch}
</CardContent>
@@ -431,7 +426,7 @@ export default function SkillsPage() {
ts.name;
return (
<Card key={ts.name} className="relative">
<Card key={ts.name} className="relative rounded-none">
<CardContent className="py-4">
<div className="flex items-start gap-3">
<TsIcon className="h-5 w-5 text-muted-foreground shrink-0 mt-0.5" />
@@ -536,7 +531,7 @@ function PanelItem({ active, icon: Icon, label, onClick }: PanelItemProps) {
active={active}
onClick={onClick}
className={cn(
"rounded-sm whitespace-nowrap px-2.5 py-1.5",
"rounded-none whitespace-nowrap px-2.5 py-1.5",
"font-mondwest text-[0.7rem] tracking-[0.08em] uppercase",
active && "bg-foreground/90 text-background hover:text-background",
)}