mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-26 04:25:46 +02:00
fix(desktop): fade tab separators on hover, correct their weight (MUL-4811) (#5500)
The tab strip's hairlines used --border, which is authored for near-white surfaces: its other uses sit on --surface-raised (L=1.0) where it gives a reasonable dL 0.055, but on --app-shell (L=0.964) it collapses to dL 0.019 -- about a quarter of the 0.068-0.080 the rest of the chrome's 1px keylines run at, so it read as almost invisible. Move both the tab separator and the pinned-zone divider to --surface-border, which the surrounding chrome (the active tab's border, the flare arcs, the content card's ring) already uses, and which is what the system draws for separators on --sidebar -- a surface whose lightness is identical to --app-shell. Also fade a hairline out while either tab it divides is hovered, so it no longer lingers 2px off the hover pill's rounded edge. The hairline sits on a tab's own left edge, so the pair's other half belongs to the neighbouring component instance and no ancestor-based variant can reach it; hence the adjacent-sibling custom variant. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -185,7 +185,10 @@ function SortableTabItem({
|
||||
canCloseOthers: boolean;
|
||||
isNew: boolean;
|
||||
shouldReduceMotion: boolean;
|
||||
/** Hairline on the tab's left edge — hidden next to the active tab. */
|
||||
/**
|
||||
* Hairline on the tab's left edge — hidden next to the active tab, and
|
||||
* faded out while either of the two tabs it divides is hovered.
|
||||
*/
|
||||
showSeparator: boolean;
|
||||
}) {
|
||||
const setActiveTab = useTabStore((s) => s.setActiveTab);
|
||||
@@ -356,9 +359,12 @@ function SortableTabItem({
|
||||
/>
|
||||
)}
|
||||
{showSeparator && (
|
||||
// Fades in step with the neighbouring hover pill: both use a bare
|
||||
// transition-opacity, so the hairline clears exactly as the pill
|
||||
// arrives rather than lingering 2px off its rounded edge.
|
||||
<span
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute left-0 top-1/2 h-4 w-px -translate-y-1/2 bg-border"
|
||||
className="pointer-events-none absolute left-0 top-1/2 h-4 w-px -translate-y-1/2 bg-surface-border transition-opacity group-hover/tab:opacity-0 prev-tab-hover:opacity-0"
|
||||
/>
|
||||
)}
|
||||
<ContextMenu>
|
||||
@@ -650,7 +656,7 @@ export function TabBar() {
|
||||
unpinnedCount > 0 && (
|
||||
<div
|
||||
aria-hidden
|
||||
className="mx-1 mb-2.5 h-4 w-px shrink-0 self-end bg-border"
|
||||
className="mx-1 mb-2.5 h-4 w-px shrink-0 self-end bg-surface-border"
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
|
||||
@@ -6,6 +6,20 @@
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
/* Tab strip hairlines sit on each tab's own left edge, so the hairline between
|
||||
two tabs belongs to the tab on the right. Hovering the left tab has to fade
|
||||
it out as well, which no ancestor-based variant can reach — hence the
|
||||
adjacent-sibling selector. The (hover: hover) gate mirrors what Tailwind
|
||||
already wraps group-hover in, so both halves of the fade stay in step on
|
||||
devices without a pointer. */
|
||||
@custom-variant prev-tab-hover {
|
||||
@media (hover: hover) {
|
||||
[data-tab-frame]:hover + [data-tab-frame] & {
|
||||
@slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Font stack: Inter for Latin UI text + system CJK fonts for localized content.
|
||||
Web app uses the same stack via next/font/google in apps/web/app/layout.tsx —
|
||||
keep the CJK fallback tail in sync across both files. The Inter primary family
|
||||
|
||||
Reference in New Issue
Block a user