Files
cinny/src/app/components/avatar-decoration/AvatarDecoration.tsx
T

72 lines
2.0 KiB
TypeScript
Raw Normal View History

2026-06-14 11:24:04 -04:00
import React from 'react';
import { useAvatarDecoration } from '../../hooks/useAvatarDecoration';
import { useReducedMotion } from '../../hooks/useReducedMotion';
2026-06-14 11:24:04 -04:00
import { decorationUrl } from '../../features/lotus/avatarDecorations';
const DEFAULT_INSET = 8;
2026-06-14 11:24:04 -04:00
type AvatarDecorationProps = {
userId: string;
children: React.ReactNode;
inset?: number;
2026-06-14 11:24:04 -04:00
};
export function AvatarDecoration({
userId,
children,
inset = DEFAULT_INSET,
}: AvatarDecorationProps) {
2026-06-14 11:24:04 -04:00
const slug = useAvatarDecoration(userId);
const reducedMotion = useReducedMotion();
2026-06-14 11:24:04 -04:00
// Decorations are animated APNGs with no static asset to freeze to, so honor
// prefers-reduced-motion by not rendering the animation at all (consistent
// with the rest of the theming stack — chat backgrounds / seasonal overlays —
// which all suppress motion under this preference; also avoids dozens of live
// APNGs animating in scrolling mobile lists).
if (!slug || reducedMotion) {
2026-06-14 11:24:04 -04:00
return <>{children}</>;
}
return (
<div
style={{
position: 'relative',
display: 'inline-flex',
flexShrink: 0,
}}
>
{children}
<img
// Force a fresh element per slug so a recycled node whose previous slug
// 404'd (and was hidden in onError) can't leak `display:none` onto a
// valid decoration.
key={slug}
2026-06-14 11:24:04 -04:00
src={decorationUrl(slug)}
style={{
position: 'absolute',
top: -inset,
left: -inset,
right: -inset,
bottom: -inset,
width: `calc(100% + ${inset * 2}px)`,
height: `calc(100% + ${inset * 2}px)`,
2026-06-14 11:24:04 -04:00
pointerEvents: 'none',
zIndex: 10,
objectFit: 'contain',
}}
alt=""
aria-hidden="true"
loading="lazy"
decoding="async"
onLoad={(e) => {
(e.currentTarget as HTMLImageElement).style.removeProperty('display');
}}
onError={(e) => {
(e.currentTarget as HTMLImageElement).style.display = 'none';
}}
2026-06-14 11:24:04 -04:00
/>
</div>
);
}