style(layout): borderless WorkspaceSwitcher with hover/open background; fix avatar jump

Matches the crewli-starter SoT and fixes the recurring collapse jump at
its root cause. The prior structures left a residual avatar shift:
- the original split put the avatar at 24px expanded (wrapper px-4 +
  card p-2) vs 16px collapsed (bare square) — an 8px horizontal jump;
- the interim single-trigger variant used wrapper p-[10px] + trigger
  px-[10px] expanded (~20px) vs justify-center collapsed (16px) — a
  ~4px residual horizontal shift.

Unified both states to a single symmetric structure:

    avatar offset = wrapper px-2 (8px) + trigger p-2 (8px) = 16px

16px from the rail's left edge in BOTH states — identical to the
SidebarHeader brand logo. Because the padding is symmetric (8 + 8 each
side) and the collapsed rail is 64px = 16 + 32 + 16, the left-aligned
avatar is also visually centred when collapsed — no justify-center,
no px swap, no horizontal shift; constant vertical padding, no vertical
shift. The jump is gone at the root.

Borderless: the trigger has NO border in any state (the prior is-open
border is dropped per the starter screenshots). The only divider is the
wrapper's border-t between the switcher and the nav. The grey
background is the sole fill — transparent at rest, grey on hover, and
grey while the popover is open (isOpen wired to Popover @show/@hide).
The trigger's p-2 gives the grey background generous padding around the
avatar+text, matching the starter's hover treatment, and since it is
the button's own background it never moves the content.

Specs reworked: trigger p-2 identical across states (no px swap / no
justify-center — the no-jump lock), wrapper carries p-2, trigger is
borderless at rest AND while open, open-state grey background applies
on @show and clears on @hide. Single-.trigger / rounded-lg / collapsed-
hides-meta+chev / sub-line specs retained.

Suite delta: 571 → 571 (specs reworked, count unchanged). vue-tsc
clean. Scoped ESLint clean (0 errors). Desktop only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-21 22:49:08 +02:00
parent 9f26215e54
commit 9f56fb1112
2 changed files with 77 additions and 60 deletions

View File

@@ -161,43 +161,41 @@ function inviteUser(): void {
<template>
<!--
Crewli-starter-faithful structure (P6-styling-switcher-sub follow-up).
ONE `.trigger` button renders in BOTH states collapsing only hides
`.meta` + `.chev` (v-if) and recentres the lone avatar. This is the
key fix for the vertical "avatar jump": the prior version swapped
between two separate <button> elements (a bare collapsed button vs a
padded expanded trigger) with different box models, so the avatar's
distance from the rail bottom differed by ~6px and visibly jumped on
collapse. With a single trigger and constant vertical padding
(`py-2` always), the avatar stays put.
Unified borderless structure (P6-styling-switcher-hover, crewli-starter
SoT). ONE `.trigger` button renders in BOTH states; collapsing only
hides `.meta` + `.chev` (v-if). The button keeps the SAME padding in
both states this is what kills the avatar jump:
Wrapper: `border-t` separator + `p-[10px]` ALWAYS (constant vertical
padding → no jump; matches crewli-starter `.ws-switcher { padding:
10px }`). No border around the wrapper itself.
avatar offset = wrapper px-2 (8px) + trigger p-2 (8px) = 16px
Centring equation (collapsed): wrapper p-[10px] gives the trigger a
44px content width inside the 64px rail; the trigger's `justify-center
px-0` centres the 32px avatar 16px from the rail's left edge,
aligned with the SidebarHeader brand logo above (also 16px).
16px from the rail's left edge in BOTH collapsed and expanded —
identical to the SidebarHeader brand logo (px-4). Because the
horizontal padding is symmetric (8 + 8 each side) and the rail
collapses to 64px = 16 + 32 (avatar) + 16, the left-aligned avatar
is ALSO visually centred when collapsed — no justify-center swap, no
horizontal shift. Vertical padding is likewise constant, so no
vertical shift either. The earlier 24px-expanded / 16px-collapsed
split (and the later px-[10px]/justify-center variant) both left a
residual shift; this symmetric structure removes it entirely.
Borderless: the trigger has NO border in any state (the prior
is-open border is dropped per the starter screenshots). The only
divider is the wrapper's `border-t` separating the switcher from the
nav above. The grey background is the sole fill, on hover OR while
the popover is open.
-->
<div class="ws-switcher relative flex-shrink-0 border-t border-[var(--p-content-border-color)] p-[10px]">
<div class="ws-switcher relative flex-shrink-0 border-t border-[var(--p-content-border-color)] p-2">
<!--
Triggersingle button for both states (crewli-starter `.trigger`).
Resting: transparent border + transparent bg. Hover: grey bg, no
border. Open (is-open): grey bg KEPT + visible border, persisting
until the popover closes — crewli-starter
`.ws-switcher.is-open .trigger { background; border-color }`.
`py-2` is constant; only horizontal padding / justify changes on
collapse, so the avatar never shifts vertically.
Trigger single button for both states. Transparent at rest; grey
on hover; grey KEPT while the popover is open (isOpen, wired to the
Popover @show/@hide). The `p-2` gives the grey background generous
padding around the avatar+text (crewli-starter hover treatment),
and because the background is the button's own bg (not a separate
shifting layer) it never moves the avatar.
-->
<button
class="trigger flex w-full items-center gap-[10px] rounded-lg border bg-transparent py-2 text-[var(--p-text-color)] transition-[background,border-color] duration-150 focus-visible:outline focus-visible:outline-2 focus-visible:outline-[var(--p-primary-color)] focus-visible:outline-offset-2"
:class="[
collapsed ? 'justify-center px-0' : 'px-[10px]',
isOpen
? 'is-open border-[var(--p-content-border-color)] bg-[var(--p-content-hover-background)]'
: 'border-transparent hover:bg-[var(--p-content-hover-background)]',
]"
class="trigger flex w-full items-center gap-[10px] rounded-lg bg-transparent p-2 text-[var(--p-text-color)] transition-colors duration-150 hover:bg-surface-100 dark:hover:bg-surface-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-[var(--p-primary-color)] focus-visible:outline-offset-2"
:class="{ 'bg-surface-100 dark:bg-surface-800': isOpen }"
aria-haspopup="true"
:aria-expanded="isOpen"
:aria-label="collapsed && current ? `Workspace: ${current.name}` : undefined"