Skip to content

Commit fe040df

Browse files
committed
Fix for the Avatar component having SSR issues. Specify the size in rems and removed the useLayoutEffect
1 parent 9ad0508 commit fe040df

File tree

4 files changed

+38
-58
lines changed

4 files changed

+38
-58
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ function ProjectSelector({
304304
)}
305305
>
306306
<span className="flex items-center gap-1.5 overflow-hidden">
307-
<Avatar avatar={organization.avatar} className="size-5" />
307+
<Avatar avatar={organization.avatar} size={1.25} />
308308
<SelectorDivider />
309309
<span className="truncate text-2sm font-normal text-text-bright">
310310
{project.name ?? "Select a project"}
@@ -319,7 +319,7 @@ function ProjectSelector({
319319
<div className="flex flex-col gap-2 bg-charcoal-750 p-2">
320320
<div className="flex items-center gap-2.5">
321321
<div className="box-content size-10 overflow-clip rounded-sm bg-charcoal-800">
322-
<Avatar avatar={organization.avatar} className="size-10" includePadding />
322+
<Avatar avatar={organization.avatar} size={2.5} includePadding />
323323
</div>
324324
<div className="space-y-0.5">
325325
<Paragraph variant="small/bright">{organization.title}</Paragraph>
@@ -483,7 +483,7 @@ function SwitchOrganizations({
483483
key={org.id}
484484
to={organizationPath(org)}
485485
title={org.title}
486-
icon={<Avatar className="size-4" avatar={org.avatar} />}
486+
icon={<Avatar size={1} avatar={org.avatar} />}
487487
leadingIconClassName="text-text-dimmed"
488488
isSelected={org.id === organization.id}
489489
/>

apps/webapp/app/components/primitives/Avatar.tsx

Lines changed: 30 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
StarIcon,
88
} from "@heroicons/react/20/solid";
99
import { type Prisma } from "@trigger.dev/database";
10-
import { useLayoutEffect, useRef, useState } from "react";
1110
import { z } from "zod";
1211
import { useOrganization } from "~/hooks/useOrganizations";
1312
import { logger } from "~/services/logger.server";
@@ -53,22 +52,21 @@ export function parseAvatar(json: Prisma.JsonValue, defaultAvatar: Avatar): Avat
5352

5453
export function Avatar({
5554
avatar,
56-
className,
55+
size,
5756
includePadding,
5857
}: {
5958
avatar: Avatar;
60-
className?: string;
59+
/** Size in rems of the icon */
60+
size: number;
6161
includePadding?: boolean;
6262
}) {
6363
switch (avatar.type) {
6464
case "icon":
65-
return <AvatarIcon avatar={avatar} className={className} includePadding={includePadding} />;
65+
return <AvatarIcon avatar={avatar} size={size} includePadding={includePadding} />;
6666
case "letters":
67-
return (
68-
<AvatarLetters avatar={avatar} className={className} includePadding={includePadding} />
69-
);
67+
return <AvatarLetters avatar={avatar} size={size} includePadding={includePadding} />;
7068
case "image":
71-
return <AvatarImage avatar={avatar} className={className} />;
69+
return <AvatarImage avatar={avatar} size={size} />;
7270
}
7371
}
7472

@@ -101,65 +99,48 @@ export const defaultAvatar: Avatar = {
10199
hex: defaultAvatarHex,
102100
};
103101

102+
function styleFromSize(size: number) {
103+
return {
104+
width: `${size}rem`,
105+
height: `${size}rem`,
106+
};
107+
}
108+
104109
function AvatarLetters({
105110
avatar,
106-
className,
111+
size,
107112
includePadding,
108113
}: {
109114
avatar: LettersAvatar;
110-
className?: string;
115+
size: number;
111116
includePadding?: boolean;
112117
}) {
113118
const organization = useOrganization();
114-
const containerRef = useRef<HTMLSpanElement>(null);
115-
const textRef = useRef<HTMLSpanElement>(null);
116-
const [fontSize, setFontSize] = useState("1rem");
117-
118-
useLayoutEffect(() => {
119-
if (containerRef.current) {
120-
const containerWidth = containerRef.current.offsetWidth;
121-
// Set font size to 60% of container width (adjust as needed)
122-
setFontSize(`${containerWidth * 0.6}px`);
123-
}
124-
125-
// Optional: Create a ResizeObserver for dynamic resizing
126-
const resizeObserver = new ResizeObserver((entries) => {
127-
for (const entry of entries) {
128-
if (entry.target === containerRef.current) {
129-
const containerWidth = entry.contentRect.width;
130-
setFontSize(`${containerWidth * 0.6}px`);
131-
}
132-
}
133-
});
134-
135-
if (containerRef.current) {
136-
resizeObserver.observe(containerRef.current);
137-
}
138-
139-
return () => {
140-
resizeObserver.disconnect();
141-
};
142-
}, []);
143-
144119
const letters = organization.title.slice(0, 2);
145120

146-
const classes = cn("grid place-items-center", className);
147121
const style = {
148122
backgroundColor: avatar.hex,
149123
};
150124

125+
const scaleFactor = includePadding ? 0.8 : 1;
126+
151127
return (
152-
<span className={cn("grid place-items-center overflow-hidden text-charcoal-750", classes)}>
128+
<span
129+
className="grid place-items-center overflow-hidden text-charcoal-750"
130+
style={styleFromSize(size)}
131+
>
153132
{/* This is the square container */}
154133
<span
155-
ref={containerRef}
156134
className={cn(
157135
"relative grid place-items-center overflow-hidden rounded-[10%] font-semibold",
158136
includePadding ? "size-[80%]" : "size-[100%]"
159137
)}
160138
style={style}
161139
>
162-
<span ref={textRef} className="font-bold leading-none" style={{ fontSize }}>
140+
<span
141+
className="font-bold leading-none"
142+
style={{ fontSize: `${size * 0.6 * scaleFactor}rem` }}
143+
>
163144
{letters}
164145
</span>
165146
</span>
@@ -169,29 +150,28 @@ function AvatarLetters({
169150

170151
function AvatarIcon({
171152
avatar,
172-
className,
153+
size,
173154
includePadding,
174155
}: {
175156
avatar: IconAvatar;
176-
className?: string;
157+
size: number;
177158
includePadding?: boolean;
178159
}) {
179-
const classes = cn("aspect-square", className);
180160
const style = {
181161
color: avatar.hex,
182162
};
183163

184164
const IconComponent = avatarIcons[avatar.name];
185165
return (
186-
<span className={cn("grid place-items-center", classes)}>
166+
<span className="grid aspect-square place-items-center" style={styleFromSize(size)}>
187167
<IconComponent className={includePadding ? "size-[80%]" : "size-[100%]"} style={style} />
188168
</span>
189169
);
190170
}
191171

192-
function AvatarImage({ avatar, className }: { avatar: ImageAvatar; className?: string }) {
172+
function AvatarImage({ avatar, size }: { avatar: ImageAvatar; size: number }) {
193173
return (
194-
<span className="grid place-items-center">
174+
<span className="grid place-items-center" style={styleFromSize(size)}>
195175
<img src={avatar.url} alt="Organization avatar" className="size-6" />
196176
</span>
197177
);

apps/webapp/app/routes/_app.orgs.$organizationSlug.settings._index/route.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ function LogoForm({ organization }: { organization: { avatar: Avatar } }) {
392392
<Label>Icon</Label>
393393
<div className="flex w-full items-end justify-between gap-2">
394394
<div className="grid place-items-center overflow-hidden rounded-sm border border-charcoal-750 bg-background-bright">
395-
<Avatar avatar={avatar} className="size-20" includePadding />
395+
<Avatar avatar={avatar} size={5} includePadding />
396396
</div>
397397
{/* Letters */}
398398
<Form method="post">
@@ -416,7 +416,7 @@ function LogoForm({ organization }: { organization: { avatar: Avatar } }) {
416416
type: "letters",
417417
hex,
418418
}}
419-
className="size-10"
419+
size={2.5}
420420
includePadding
421421
/>
422422
</button>
@@ -447,7 +447,7 @@ function LogoForm({ organization }: { organization: { avatar: Avatar } }) {
447447
name,
448448
hex,
449449
}}
450-
className="size-10"
450+
size={2.5}
451451
includePadding
452452
/>
453453
</button>

apps/webapp/app/routes/storybook.avatar/route.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export default function Story() {
2020
<h2 className="mb-4 text-lg font-semibold text-white">Size 8</h2>
2121
<div className="flex flex-wrap gap-2">
2222
{avatars.map((avatar, index) => (
23-
<Avatar key={`small-${index}`} avatar={avatar} className="size-8" />
23+
<Avatar key={`small-${index}`} avatar={avatar} size={2} />
2424
))}
2525
</div>
2626
</div>
@@ -30,7 +30,7 @@ export default function Story() {
3030
<h2 className="mb-4 text-lg font-semibold text-white">Size 12</h2>
3131
<div className="flex flex-wrap gap-4">
3232
{avatars.map((avatar, index) => (
33-
<Avatar key={`large-${index}`} avatar={avatar} className="size-12" />
33+
<Avatar key={`large-${index}`} avatar={avatar} size={3} />
3434
))}
3535
</div>
3636
</div>

0 commit comments

Comments
 (0)