0b2919465c
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
63 lines
1.4 KiB
TypeScript
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)} />;
|
|
}
|