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" />
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
} from "~/assets/icons/EnvironmentIcons";
|
||||
import type { RuntimeEnvironment } from "~/models/runtimeEnvironment.server";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { labelOverflowFadeStyle } from "~/components/primitives/labelOverflowFade";
|
||||
import { SimpleTooltip } from "~/components/primitives/Tooltip";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
@@ -88,7 +89,7 @@ export function EnvironmentLabel({
|
||||
tooltipSideOffset?: number;
|
||||
tooltipSide?: "top" | "right" | "bottom" | "left";
|
||||
disableTooltip?: boolean;
|
||||
/** When false, the label clips without an ellipsis (side menu fades it in place). Defaults true. */
|
||||
/** When false, an overflowing label fades at its right edge instead of ending in an ellipsis. */
|
||||
truncate?: boolean;
|
||||
}) {
|
||||
const spanRef = useRef<HTMLSpanElement>(null);
|
||||
@@ -122,6 +123,7 @@ export function EnvironmentLabel({
|
||||
environmentTextClassName(environment),
|
||||
className
|
||||
)}
|
||||
style={labelOverflowFadeStyle(!truncate && isTruncated)}
|
||||
>
|
||||
{text}
|
||||
</span>
|
||||
|
||||
@@ -94,7 +94,7 @@ export function EnvironmentSelector({
|
||||
fades in place and scales its width to 0 so it never holds width mid-drag. The
|
||||
selector is also reused outside the side menu (BlankStatePanels, limits) where the var
|
||||
is unset — the 0.2 max-width fallback pins a ~200px cap (0.2 * 1000px) so long names
|
||||
ellipsis-truncate there instead of widening the control, while opacity stays 1.
|
||||
fade out there instead of widening the control, while opacity stays 1.
|
||||
*/}
|
||||
<span
|
||||
className="flex min-w-0 items-center overflow-hidden"
|
||||
@@ -105,7 +105,7 @@ export function EnvironmentSelector({
|
||||
>
|
||||
<EnvironmentLabel
|
||||
environment={environment}
|
||||
className="text-ellipsis text-[0.90625rem] font-medium tracking-[-0.01em]"
|
||||
className="text-[0.90625rem] font-medium tracking-[-0.01em]"
|
||||
disableTooltip
|
||||
truncate={false}
|
||||
/>
|
||||
|
||||
@@ -1682,9 +1682,9 @@ function OrgSelector({
|
||||
className="flex min-w-0 items-center gap-1.5 overflow-hidden"
|
||||
style={SIDE_MENU_SELECTOR_LABEL_STYLE}
|
||||
>
|
||||
<span className="overflow-hidden whitespace-nowrap text-[0.90625rem] font-medium tracking-[-0.01em] text-text-bright">
|
||||
<SideMenuLabel className="text-[0.90625rem] font-medium tracking-[-0.01em] text-text-bright">
|
||||
{organization.title}
|
||||
</span>
|
||||
</SideMenuLabel>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
@@ -1987,9 +1987,9 @@ function ProjectSelector({
|
||||
className="flex min-w-0 items-center overflow-hidden"
|
||||
style={SIDE_MENU_SELECTOR_LABEL_STYLE}
|
||||
>
|
||||
<span className="overflow-hidden whitespace-nowrap text-[0.90625rem] font-medium tracking-[-0.01em] text-text-bright">
|
||||
<SideMenuLabel className="text-[0.90625rem] font-medium tracking-[-0.01em] text-text-bright">
|
||||
{project.name ?? "Select a project"}
|
||||
</span>
|
||||
</SideMenuLabel>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
@@ -2041,7 +2041,7 @@ function ProjectSelector({
|
||||
to={v3ProjectPath(organization, p)}
|
||||
title={
|
||||
<div className="flex w-full items-center justify-between text-text-bright">
|
||||
<span className="grow truncate text-left">{p.name}</span>
|
||||
<SideMenuLabel className="min-w-0 grow text-left">{p.name}</SideMenuLabel>
|
||||
</div>
|
||||
}
|
||||
isSelected={isSelected}
|
||||
|
||||
@@ -12,12 +12,10 @@ import { motion } from "framer-motion";
|
||||
import { usePathName } from "~/hooks/usePathName";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { type RenderIcon, Icon } from "../primitives/Icon";
|
||||
import { labelOverflowFadeStyle } from "../primitives/labelOverflowFade";
|
||||
import { SimpleTooltip } from "../primitives/Tooltip";
|
||||
import { useActiveFavoriteId } from "./favoritePages";
|
||||
|
||||
/** Right-edge fade shown instead of a hard clip, only while the label actually overflows. */
|
||||
const LABEL_OVERFLOW_MASK = "linear-gradient(to right, black calc(100% - 1.5rem), transparent)";
|
||||
|
||||
/**
|
||||
* A menu label that fades out at its right edge when (and only when) the text overflows. Text
|
||||
* that fits renders exactly as before, with no mask. Overflow is re-measured when the element
|
||||
@@ -56,12 +54,7 @@ export function SideMenuLabel({
|
||||
<span
|
||||
ref={ref}
|
||||
className={cn("overflow-hidden whitespace-nowrap", className)}
|
||||
style={{
|
||||
...style,
|
||||
...(isOverflowing
|
||||
? { maskImage: LABEL_OVERFLOW_MASK, WebkitMaskImage: LABEL_OVERFLOW_MASK }
|
||||
: undefined),
|
||||
}}
|
||||
style={{ ...style, ...labelOverflowFadeStyle(isOverflowing) }}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
const LABEL_OVERFLOW_MASK = "linear-gradient(to right, black calc(100% - 1.5rem), transparent)";
|
||||
|
||||
export function labelOverflowFadeStyle(isOverflowing: boolean) {
|
||||
return isOverflowing
|
||||
? { maskImage: LABEL_OVERFLOW_MASK, WebkitMaskImage: LABEL_OVERFLOW_MASK }
|
||||
: undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user