53 lines
1.7 KiB
TypeScript
53 lines
1.7 KiB
TypeScript
import type { ReactElement, ReactNode, SyntheticEvent } from 'react';
|
|
import { CameraIcon } from '../../../shared/ui';
|
|
import { useMediaViewer } from '../../messaging';
|
|
|
|
interface Props {
|
|
url: string | null;
|
|
/** Placeholder content when there's no photo (initial letter or an icon). */
|
|
fallback: ReactNode;
|
|
alt: string;
|
|
/** When provided, shows a camera overlay that lets the user pick a new photo. */
|
|
onPick?: (file: File) => void;
|
|
}
|
|
|
|
// Profile/group hero avatar: click the photo to view it full-size in the
|
|
// lightbox; an optional camera overlay lets owners change it. Must be rendered
|
|
// inside a MediaViewerProvider.
|
|
export const HeroAvatar = ({ url, fallback, alt, onPick }: Props): ReactElement => {
|
|
const viewer = useMediaViewer();
|
|
|
|
const onFile = (event: SyntheticEvent<HTMLInputElement>): void => {
|
|
const input = event.currentTarget;
|
|
const file = input.files?.[0];
|
|
input.value = '';
|
|
if (file !== undefined) {
|
|
onPick?.(file);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="hero-avatar-wrap">
|
|
{url !== null ? (
|
|
<button
|
|
type="button"
|
|
className="hero-avatar-view"
|
|
aria-label="View photo"
|
|
onClick={() => {
|
|
viewer.open({ type: 'image', url, name: alt });
|
|
}}
|
|
>
|
|
<img src={url} alt="" className="profile-avatar" />
|
|
</button>
|
|
) : (
|
|
<span className="profile-avatar avatar-placeholder">{fallback}</span>
|
|
)}
|
|
{onPick !== undefined ? (
|
|
<label className="hero-avatar-edit" title="Change photo">
|
|
<CameraIcon size={18} />
|
|
<input type="file" accept="image/*" hidden onChange={onFile} />
|
|
</label>
|
|
) : null}
|
|
</div>
|
|
);
|
|
};
|