Skip to content

Commit 55be043

Browse files
committed
feat(webapp): fade side menu labels out in place instead of truncating
While the side menu is dragged or animated between open and collapsed, the button and nav labels used to shrink their text box in lockstep with the fade, so the text visibly truncated (with an ellipsis) as the row narrowed. Drive the visible fade purely by opacity so the text stays at full width and simply fades in place. Labels that are wider than the row still clip at its edge, now without an ellipsis. The selector label width cap still scales to zero as the label finishes fading, so an invisible label never holds layout width (which would push the row's clip edge into the icon). EnvironmentLabel gains a `truncate` prop (default true) so only the side menu opts out; every other usage is unchanged.
1 parent 49f6968 commit 55be043

5 files changed

Lines changed: 31 additions & 15 deletions

File tree

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,16 @@ export function EnvironmentLabel({
8181
tooltipSideOffset = 34,
8282
tooltipSide = "right",
8383
disableTooltip = false,
84+
truncate = true,
8485
}: {
8586
environment: Environment;
8687
className?: string;
8788
tooltipSideOffset?: number;
8889
tooltipSide?: "top" | "right" | "bottom" | "left";
8990
disableTooltip?: boolean;
91+
/** When false, the label clips without an ellipsis (used by the resizable side menu so the label
92+
* fades out in place while dragging instead of truncating). Defaults to true everywhere else. */
93+
truncate?: boolean;
9094
}) {
9195
const spanRef = useRef<HTMLSpanElement>(null);
9296
const [isTruncated, setIsTruncated] = useState(false);
@@ -113,7 +117,12 @@ export function EnvironmentLabel({
113117
const content = (
114118
<span
115119
ref={spanRef}
116-
className={cn("truncate text-left", environmentTextClassName(environment), className)}
120+
className={cn(
121+
truncate ? "truncate" : "overflow-hidden whitespace-nowrap",
122+
"text-left",
123+
environmentTextClassName(environment),
124+
className
125+
)}
117126
>
118127
{text}
119128
</span>

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,23 +92,27 @@ export function EnvironmentSelector({
9292
<span className="flex min-w-0 flex-1 items-center gap-1.5 overflow-hidden">
9393
<EnvironmentIcon environment={environment} className="size-5 shrink-0" />
9494
{/*
95-
Width and opacity are driven by the resizable SideMenu's `--sm-label-opacity`
96-
variable so the label tracks a drag frame-by-frame in both directions (gating on
97-
isCollapsed, which only flips on release, made the label pop in after a drag-open).
95+
Opacity is driven by the resizable SideMenu's `--sm-label-opacity` variable so the
96+
label fades frame-by-frame in both directions (gating on isCollapsed, which only flips
97+
on release, made the label pop in after a drag-open). The max-width cap is generous
98+
(far wider than any env name) so the visible fade is driven purely by opacity — the
99+
text stays full and fades out in place instead of truncating as the row narrows —
100+
while still reaching 0 as the label finishes fading so it never holds layout width.
98101
Unset in the other places this selector is used (blank-state panels, Limits page)
99102
→ falls back to fully visible.
100103
*/}
101104
<span
102105
className="flex min-w-0 items-center overflow-hidden"
103106
style={{
104-
maxWidth: "calc(var(--sm-label-opacity, 1) * 200px)",
107+
maxWidth: "calc(var(--sm-label-opacity, 1) * 1000px)",
105108
opacity: "var(--sm-label-opacity, 1)",
106109
}}
107110
>
108111
<EnvironmentLabel
109112
environment={environment}
110113
className="text-[0.90625rem] font-medium tracking-[-0.01em]"
111114
disableTooltip
115+
truncate={false}
112116
/>
113117
</span>
114118
</span>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export function HelpAndFeedback({
7878
transitions: they would lag the per-frame variable writes.
7979
*/}
8080
<span
81-
className="min-w-0 truncate text-[0.90625rem] font-medium tracking-[-0.01em] text-text-dimmed group-hover:text-text-bright"
81+
className="min-w-0 overflow-hidden whitespace-nowrap text-[0.90625rem] font-medium tracking-[-0.01em] text-text-dimmed group-hover:text-text-bright"
8282
style={{
8383
maxWidth: "calc(var(--sm-label-opacity, 1) * 150px)",
8484
opacity: "var(--sm-label-opacity, 1)",

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

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -225,14 +225,17 @@ const SIDE_MENU_CHEVRON_STYLE = {
225225
maxWidth: "calc(var(--sm-label-opacity, 1) * 16px)",
226226
} as const;
227227
/**
228-
* The selector rows' label container (org/project/environment): width and opacity both follow
229-
* --sm-label-opacity so the label tracks a drag frame-by-frame in BOTH directions. Gating these on
230-
* `isCollapsed` (which only flips on release) made the labels pop in after a drag-open instead of
231-
* fading in like the nav items. The variable also animates during the click-toggle, and is 0/1 at
232-
* the collapsed/expanded resting states, so no isCollapsed classes or CSS transitions are needed.
228+
* The selector rows' label container (org/project/environment): opacity follows --sm-label-opacity
229+
* so the label fades frame-by-frame in BOTH directions (gating on `isCollapsed`, which only flips on
230+
* release, made labels pop in after a drag-open instead of fading). The max-width cap is deliberately
231+
* generous (far wider than any label) so the visible fade is driven purely by opacity — the text
232+
* stays full and fades out in place rather than being truncated as the row narrows. Because the cap
233+
* still scales with the variable it reaches 0 exactly as the label finishes fading, so an invisible
234+
* label never holds layout width (which would push the row's clip edge into the icon). No isCollapsed
235+
* classes or CSS transitions are needed: the variable is 0/1 at the collapsed/expanded resting states.
233236
*/
234237
const SIDE_MENU_SELECTOR_LABEL_STYLE = {
235-
maxWidth: "calc(var(--sm-label-opacity, 1) * 200px)",
238+
maxWidth: "calc(var(--sm-label-opacity, 1) * 1000px)",
236239
opacity: "var(--sm-label-opacity, 1)",
237240
} as const;
238241

@@ -1207,7 +1210,7 @@ function OrgSelector({
12071210
className="flex min-w-0 items-center gap-1.5 overflow-hidden"
12081211
style={SIDE_MENU_SELECTOR_LABEL_STYLE}
12091212
>
1210-
<span className="truncate text-[0.90625rem] font-medium tracking-[-0.01em] text-text-bright">
1213+
<span className="overflow-hidden whitespace-nowrap text-[0.90625rem] font-medium tracking-[-0.01em] text-text-bright">
12111214
{organization.title}
12121215
</span>
12131216
</span>
@@ -1497,7 +1500,7 @@ function ProjectSelector({
14971500
className="flex min-w-0 items-center overflow-hidden"
14981501
style={SIDE_MENU_SELECTOR_LABEL_STYLE}
14991502
>
1500-
<span className="truncate text-[0.90625rem] font-medium tracking-[-0.01em] text-text-bright">
1503+
<span className="overflow-hidden whitespace-nowrap text-[0.90625rem] font-medium tracking-[-0.01em] text-text-bright">
15011504
{project.name ?? "Select a project"}
15021505
</span>
15031506
</span>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export function SideMenuItem({
100100
>
101101
<span
102102
className={cn(
103-
"select-none truncate text-[0.90625rem] font-medium tracking-[-0.01em]",
103+
"select-none overflow-hidden whitespace-nowrap text-[0.90625rem] font-medium tracking-[-0.01em]",
104104
nameClassName
105105
)}
106106
>

0 commit comments

Comments
 (0)