Files
James Ritchie 0b2919465c feat(webapp): redesign the side menu project and organization menus (#4066)
Redesign of the main side menu: separates Projects and Accounts from the
Organization menu and makes the menu resizable.

**Main changes**

- **Organization & Account menus**: the top-left is now a dedicated
organization menu (Settings, Usage, Billing, Team, SSO, integrations),
with a separate account menu beside it (Profile, PATs, Security,
Logout).
- **Project switcher**: a new Project section above the Environment
selector.
- **Resizable side menu**: drag the right edge to set a custom width
(saved per user), or click the edge to collapse/expand.
- **Environment selector**: reworked to match the Project menu,
including dev-branch handling.
- **Account Profile page**: redesigned into the Security page's
row-and-divider layout.

Preview URL: https://samejr-org-menu-update.triggerlabs.dev/



https://github.com/user-attachments/assets/9b199576-6037-4ea6-9bdb-3ee15265b8c2
2026-07-21 17:25:17 +01:00

63 lines
1.4 KiB
TypeScript

import {
AvatarCircleIcon,
AvatarCircleIconExtraThin,
AvatarCircleIconThin,
} from "~/assets/icons/AvatarCircleIcon";
import { useOptionalUser } from "~/hooks/useUser";
import { cn } from "~/utils/cn";
/** Stroke width (px) of the placeholder avatar icon shown when there is no photo. */
type AvatarStrokeWidth = 1.25 | 1.5 | 2;
const PLACEHOLDER_BY_STROKE_WIDTH = {
1.25: AvatarCircleIconExtraThin,
1.5: AvatarCircleIconThin,
2: AvatarCircleIcon,
} as const;
export function UserProfilePhoto({
className,
strokeWidth = 2,
}: {
className?: string;
strokeWidth?: AvatarStrokeWidth;
}) {
const user = useOptionalUser();
return (
<UserAvatar
avatarUrl={user?.avatarUrl}
name={user?.name}
className={className}
strokeWidth={strokeWidth}
/>
);
}
export function UserAvatar({
avatarUrl,
name,
className,
strokeWidth = 2,
}: {
avatarUrl?: string | null;
name?: string | null;
className?: string;
strokeWidth?: AvatarStrokeWidth;
}) {
if (avatarUrl) {
return (
<div className={cn("grid aspect-square place-items-center", className)}>
<img
className={cn("aspect-square rounded-full p-[7%]")}
src={avatarUrl}
alt={name ?? "User"}
referrerPolicy="no-referrer"
/>
</div>
);
}
const PlaceholderIcon = PLACEHOLDER_BY_STROKE_WIDTH[strokeWidth];
return <PlaceholderIcon className={cn("aspect-square text-text-dimmed", className)} />;
}