Skip to content

Commit a11e5ff

Browse files
authored
fix(webapp): fade overflowing side menu selector labels (#4412)
Long organization, project, and environment names in the side menu were cut off mid-character. They now fade out at the right edge like the rest of the side menu items already did. ### Example of faded long names: <img width="246" height="200" alt="CleanShot 2026-07-28 at 22 59 24" src="https://github.com/user-attachments/assets/efa60b87-286f-4ab0-9d4e-490ef2de53e5" />
1 parent 1e14e29 commit a11e5ff

5 files changed

Lines changed: 19 additions & 17 deletions

File tree

apps/webapp/app/components/environments/EnvironmentLabel.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
} from "~/assets/icons/EnvironmentIcons";
77
import type { RuntimeEnvironment } from "~/models/runtimeEnvironment.server";
88
import { cn } from "~/utils/cn";
9+
import { labelOverflowFadeStyle } from "~/components/primitives/labelOverflowFade";
910
import { SimpleTooltip } from "~/components/primitives/Tooltip";
1011
import { useEffect, useRef, useState } from "react";
1112

@@ -88,7 +89,7 @@ export function EnvironmentLabel({
8889
tooltipSideOffset?: number;
8990
tooltipSide?: "top" | "right" | "bottom" | "left";
9091
disableTooltip?: boolean;
91-
/** When false, the label clips without an ellipsis (side menu fades it in place). Defaults true. */
92+
/** When false, an overflowing label fades at its right edge instead of ending in an ellipsis. */
9293
truncate?: boolean;
9394
}) {
9495
const spanRef = useRef<HTMLSpanElement>(null);
@@ -122,6 +123,7 @@ export function EnvironmentLabel({
122123
environmentTextClassName(environment),
123124
className
124125
)}
126+
style={labelOverflowFadeStyle(!truncate && isTruncated)}
125127
>
126128
{text}
127129
</span>

apps/webapp/app/components/navigation/EnvironmentSelector.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export function EnvironmentSelector({
9494
fades in place and scales its width to 0 so it never holds width mid-drag. The
9595
selector is also reused outside the side menu (BlankStatePanels, limits) where the var
9696
is unset — the 0.2 max-width fallback pins a ~200px cap (0.2 * 1000px) so long names
97-
ellipsis-truncate there instead of widening the control, while opacity stays 1.
97+
fade out there instead of widening the control, while opacity stays 1.
9898
*/}
9999
<span
100100
className="flex min-w-0 items-center overflow-hidden"
@@ -105,7 +105,7 @@ export function EnvironmentSelector({
105105
>
106106
<EnvironmentLabel
107107
environment={environment}
108-
className="text-ellipsis text-[0.90625rem] font-medium tracking-[-0.01em]"
108+
className="text-[0.90625rem] font-medium tracking-[-0.01em]"
109109
disableTooltip
110110
truncate={false}
111111
/>

apps/webapp/app/components/navigation/SideMenu.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1682,9 +1682,9 @@ function OrgSelector({
16821682
className="flex min-w-0 items-center gap-1.5 overflow-hidden"
16831683
style={SIDE_MENU_SELECTOR_LABEL_STYLE}
16841684
>
1685-
<span className="overflow-hidden whitespace-nowrap text-[0.90625rem] font-medium tracking-[-0.01em] text-text-bright">
1685+
<SideMenuLabel className="text-[0.90625rem] font-medium tracking-[-0.01em] text-text-bright">
16861686
{organization.title}
1687-
</span>
1687+
</SideMenuLabel>
16881688
</span>
16891689
</span>
16901690
<span
@@ -1987,9 +1987,9 @@ function ProjectSelector({
19871987
className="flex min-w-0 items-center overflow-hidden"
19881988
style={SIDE_MENU_SELECTOR_LABEL_STYLE}
19891989
>
1990-
<span className="overflow-hidden whitespace-nowrap text-[0.90625rem] font-medium tracking-[-0.01em] text-text-bright">
1990+
<SideMenuLabel className="text-[0.90625rem] font-medium tracking-[-0.01em] text-text-bright">
19911991
{project.name ?? "Select a project"}
1992-
</span>
1992+
</SideMenuLabel>
19931993
</span>
19941994
</span>
19951995
<span
@@ -2041,7 +2041,7 @@ function ProjectSelector({
20412041
to={v3ProjectPath(organization, p)}
20422042
title={
20432043
<div className="flex w-full items-center justify-between text-text-bright">
2044-
<span className="grow truncate text-left">{p.name}</span>
2044+
<SideMenuLabel className="min-w-0 grow text-left">{p.name}</SideMenuLabel>
20452045
</div>
20462046
}
20472047
isSelected={isSelected}

apps/webapp/app/components/navigation/SideMenuItem.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,10 @@ import { motion } from "framer-motion";
1212
import { usePathName } from "~/hooks/usePathName";
1313
import { cn } from "~/utils/cn";
1414
import { type RenderIcon, Icon } from "../primitives/Icon";
15+
import { labelOverflowFadeStyle } from "../primitives/labelOverflowFade";
1516
import { SimpleTooltip } from "../primitives/Tooltip";
1617
import { useActiveFavoriteId } from "./favoritePages";
1718

18-
/** Right-edge fade shown instead of a hard clip, only while the label actually overflows. */
19-
const LABEL_OVERFLOW_MASK = "linear-gradient(to right, black calc(100% - 1.5rem), transparent)";
20-
2119
/**
2220
* A menu label that fades out at its right edge when (and only when) the text overflows. Text
2321
* that fits renders exactly as before, with no mask. Overflow is re-measured when the element
@@ -56,12 +54,7 @@ export function SideMenuLabel({
5654
<span
5755
ref={ref}
5856
className={cn("overflow-hidden whitespace-nowrap", className)}
59-
style={{
60-
...style,
61-
...(isOverflowing
62-
? { maskImage: LABEL_OVERFLOW_MASK, WebkitMaskImage: LABEL_OVERFLOW_MASK }
63-
: undefined),
64-
}}
57+
style={{ ...style, ...labelOverflowFadeStyle(isOverflowing) }}
6558
>
6659
{children}
6760
</span>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const LABEL_OVERFLOW_MASK = "linear-gradient(to right, black calc(100% - 1.5rem), transparent)";
2+
3+
export function labelOverflowFadeStyle(isOverflowing: boolean) {
4+
return isOverflowing
5+
? { maskImage: LABEL_OVERFLOW_MASK, WebkitMaskImage: LABEL_OVERFLOW_MASK }
6+
: undefined;
7+
}

0 commit comments

Comments
 (0)