Sidebar: give the collapse arrow one home in the rail's top-right
On desktop the collapse/expand control swapped places between states: in the brand row when expanded, but a nav row in the middle of the icon column when collapsed — it read as the arrow jumping around. - One button for both directions, anchored to the rail's top-right corner in both states (measured 23px from the rail's right edge either way; the padding accounts for the rest). - Collapsed, the brand mark leads the icon column (it cannot share the brand row with the arrow: the 60px panel has ~40px of content width). - The expanded state is unchanged (logo + name + DEV badge, arrow top right). README: note in decision 22. SW -> freezmo-v41.
This commit is contained in:
parent
b5269d2844
commit
c13b1865c5
3 changed files with 47 additions and 49 deletions
|
|
@ -1054,7 +1054,9 @@ Things I chose while building, with the reasoning:
|
|||
rounded glass panel inset from the viewport edges, so the ice scene shows around it; deck cards
|
||||
are 24px-radius glass panels with a secondary Study CTA and quiet metadata. Headings are
|
||||
semibold with tighter tracking. Nested glass (chips, inputs) is more opaque but never adds a
|
||||
second blur layer.
|
||||
second blur layer. The collapse/expand arrow keeps one home — the rail's top-right corner in
|
||||
both states (it used to become a nav row in the middle of the icon column when collapsed,
|
||||
which read as the control jumping around); collapsed, the brand mark leads the icon column.
|
||||
23. **One fixed ice scene instead of per-page blobs.** The decorative background used to be
|
||||
rendered inside the content column, so on wide screens it was clipped into a visible hard edge.
|
||||
It now lives in a single `position: fixed` layer (`.ice-scene`, `z-index: -1`, pointer-events
|
||||
|
|
|
|||
|
|
@ -90,47 +90,57 @@ function NavContent({ collapsed = false }: { collapsed?: boolean }) {
|
|||
</button>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
{/* brand ---------------------------------------------------------- */}
|
||||
<div className={cn("mb-1 flex items-center", collapsed ? "justify-center" : "justify-between")}>
|
||||
<Link
|
||||
href="/"
|
||||
className={cn("nav-row gap-2.5", collapsed ? "w-auto px-2" : "flex-1")}
|
||||
aria-label={t("header.home")}
|
||||
title="Freezmo"
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element -- static app icon, no optimisation needed */}
|
||||
const brandMark = (
|
||||
// eslint-disable-next-line @next/next/no-img-element -- static app icon, no optimisation needed
|
||||
<img
|
||||
src="/icons/icon-192.png"
|
||||
alt=""
|
||||
aria-hidden
|
||||
className="size-7 shrink-0 rounded-lg shadow-[0_1px_3px_rgb(14_21_40/0.35)]"
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
{/* brand ---------------------------------------------------------- */}
|
||||
<div className={cn("mb-1 flex items-center", collapsed ? "justify-end" : "justify-between")}>
|
||||
{!collapsed ? (
|
||||
<>
|
||||
<Link
|
||||
href="/"
|
||||
className="nav-row flex-1 gap-2.5"
|
||||
aria-label={t("header.home")}
|
||||
title="Freezmo"
|
||||
>
|
||||
{brandMark}
|
||||
<span className="text-[15px] font-semibold tracking-[-0.01em] text-[var(--text)]">Freezmo</span>
|
||||
{IS_DEV_INSTANCE ? (
|
||||
<span className="rounded-md border border-amber-400/60 bg-amber-400/15 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide text-amber-700 dark:text-amber-300">
|
||||
DEV
|
||||
</span>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
</Link>
|
||||
{!collapsed ? (
|
||||
) : null}
|
||||
{/* One home for the arrow: the rail's top-right corner in both states.
|
||||
It used to swap between the brand row and the middle of the icon
|
||||
column, which read as the control jumping around. */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => sidebarCollapse.set(true)}
|
||||
aria-label={t("sidebar.collapse")}
|
||||
title={t("sidebar.collapse")}
|
||||
className="grid size-8 place-items-center rounded-lg text-[var(--text-subtle)] transition hover:bg-[var(--hover)] hover:text-[var(--text)]"
|
||||
onClick={() => sidebarCollapse.set(!collapsed)}
|
||||
aria-label={collapsed ? t("sidebar.expand") : t("sidebar.collapse")}
|
||||
title={collapsed ? t("sidebar.expand") : t("sidebar.collapse")}
|
||||
className="grid size-8 shrink-0 place-items-center rounded-lg text-[var(--text-subtle)] transition hover:bg-[var(--hover)] hover:text-[var(--text)]"
|
||||
>
|
||||
<ChevronLeftIcon className="size-4" />
|
||||
{collapsed ? <ChevronRightIcon className="size-4" /> : <ChevronLeftIcon className="size-4" />}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{/* collapsed: the brand mark leads the icon column ------------------ */}
|
||||
{collapsed ? (
|
||||
<Link href="/" className="nav-row mb-3 justify-center px-2" aria-label={t("header.home")} title="Freezmo">
|
||||
{brandMark}
|
||||
</Link>
|
||||
) : null}
|
||||
|
||||
{/* new deck: a quiet row, not a competing primary button ---------- */}
|
||||
<Link
|
||||
href="/create"
|
||||
|
|
@ -162,20 +172,6 @@ function NavContent({ collapsed = false }: { collapsed?: boolean }) {
|
|||
{!collapsed ? <span className="flex-1 truncate text-left">{t("chat.nav")}</span> : null}
|
||||
</Link>
|
||||
|
||||
{/* The expand control sits outside the swappable section, so it stays
|
||||
reachable when the chat list replaces the deck navigation. */}
|
||||
{collapsed ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => sidebarCollapse.set(false)}
|
||||
aria-label={t("sidebar.expand")}
|
||||
title={t("sidebar.expand")}
|
||||
className="nav-row mb-1 justify-center px-2"
|
||||
>
|
||||
<ChevronRightIcon className="size-4" />
|
||||
</button>
|
||||
) : null}
|
||||
|
||||
{/* On the Chat route the chat list takes the deck navigation's place. */}
|
||||
{onChat ? <SidebarChatSection collapsed={collapsed} /> : null}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
*
|
||||
* Bumping CACHE_VERSION is how a new deploy invalidates old assets.
|
||||
*/
|
||||
const CACHE_VERSION = "freezmo-v40";
|
||||
const CACHE_VERSION = "freezmo-v41";
|
||||
const SHELL_CACHE = `${CACHE_VERSION}-shell`;
|
||||
const ASSET_CACHE = `${CACHE_VERSION}-assets`;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue