addons/isl/src/Avatar.tsxblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import type {DetailedHTMLProps} from 'react';
b69ab319
b69ab3110import * as stylex from '@stylexjs/stylex';
b69ab3111import {useAtomValue} from 'jotai';
b69ab3112import {colors, radius} from '../../components/theme/tokens.stylex';
b69ab3113import serverAPI from './ClientToServerAPI';
b69ab3114import {t} from './i18n';
b69ab3115import {atomFamilyWeak, lazyAtom} from './jotaiUtils';
b69ab3116
b69ab3117const avatarUrl = atomFamilyWeak((author: string) => {
b69ab3118 // Rate limitor for the same author is by lazyAtom and atomFamilyWeak caching.
b69ab3119 return lazyAtom(async () => {
b69ab3120 serverAPI.postMessage({
b69ab3121 type: 'fetchAvatars',
b69ab3122 authors: [author],
b69ab3123 });
b69ab3124 const result = await serverAPI.nextMessageMatching('fetchedAvatars', ({authors}) =>
b69ab3125 authors.includes(author),
b69ab3126 );
b69ab3127 return result.avatars.get(author);
b69ab3128 }, undefined);
b69ab3129});
b69ab3130
b69ab3131export function AvatarImg({
b69ab3132 url,
b69ab3133 username,
b69ab3134 xstyle,
b69ab3135 ...rest
b69ab3136}: {url?: string; username: string; xstyle?: stylex.StyleXStyles} & DetailedHTMLProps<
b69ab3137 React.ImgHTMLAttributes<HTMLImageElement>,
b69ab3138 HTMLImageElement
b69ab3139>) {
b69ab3140 return url == null ? null : (
b69ab3141 <img
b69ab3142 {...stylex.props(styles.circle, xstyle)}
b69ab3143 src={url}
b69ab3144 width={14}
b69ab3145 height={14}
b69ab3146 alt={t("$user's avatar photo", {replace: {$user: username}})}
b69ab3147 {...rest}
b69ab3148 />
b69ab3149 );
b69ab3150}
b69ab3151
b69ab3152const styles = stylex.create({
b69ab3153 circle: {
b69ab3154 width: 14,
b69ab3155 height: 14,
b69ab3156 border: '2px solid',
b69ab3157 borderRadius: radius.full,
b69ab3158 borderColor: colors.fg,
b69ab3159 },
b69ab3160 empty: {
b69ab3161 content: '',
b69ab3162 backgroundColor: 'var(--foreground)',
b69ab3163 },
b69ab3164});
b69ab3165
b69ab3166export function BlankAvatar() {
b69ab3167 return <div {...stylex.props(styles.circle, styles.empty)} />;
b69ab3168}
b69ab3169
b69ab3170export function Avatar({
b69ab3171 username,
b69ab3172 ...rest
b69ab3173}: {username: string} & DetailedHTMLProps<
b69ab3174 React.ImgHTMLAttributes<HTMLImageElement>,
b69ab3175 HTMLImageElement
b69ab3176>) {
b69ab3177 const url = useAtomValue(avatarUrl(username));
b69ab3178 return url == null ? <BlankAvatar /> : <AvatarImg url={url} username={username} {...rest} />;
b69ab3179}
b69ab3180
b69ab3181/** Render as a SVG pattern */
b69ab3182export function AvatarPattern({
b69ab3183 username,
b69ab3184 size,
b69ab3185 id,
b69ab3186 fallbackFill,
b69ab3187}: {
b69ab3188 username: string;
b69ab3189 size: number;
b69ab3190 id: string;
b69ab3191 fallbackFill: string;
b69ab3192}) {
b69ab3193 const img = useAtomValue(avatarUrl(username));
b69ab3194 return (
b69ab3195 <pattern
b69ab3196 id={id}
b69ab3197 patternUnits="userSpaceOnUse"
b69ab3198 width={size}
b69ab3199 height={size}
b69ab31100 x={-size / 2}
b69ab31101 y={-size / 2}>
b69ab31102 <rect width={size} height={size} fill={fallbackFill} strokeWidth={0} />
b69ab31103 <image href={img} width={size} height={size} />
b69ab31104 </pattern>
b69ab31105 );
b69ab31106}