A React component library for rendering common AT Protocol records for applications such as Bluesky and Leaflet.
1import React from 'react'; 2import { AtProtoRecord } from '../core/AtProtoRecord'; 3import { BlueskyProfileRenderer } from '../renderers/BlueskyProfileRenderer'; 4import type { ProfileRecord } from '../types/bluesky'; 5import { useBlob } from '../hooks/useBlob'; 6import { getAvatarCid } from '../utils/profile'; 7import { useDidResolution } from '../hooks/useDidResolution'; 8import { formatDidForLabel } from '../utils/at-uri'; 9 10/** 11 * Props used to render a Bluesky actor profile record. 12 */ 13export interface BlueskyProfileProps { 14 /** 15 * DID of the target actor whose profile should be loaded. 16 */ 17 did: string; 18 /** 19 * Record key within the profile collection. Typically `'self'`. 20 */ 21 rkey?: string; 22 /** 23 * Optional renderer override for custom presentation. 24 */ 25 renderer?: React.ComponentType<BlueskyProfileRendererInjectedProps>; 26 /** 27 * Fallback node shown before a request begins yielding data. 28 */ 29 fallback?: React.ReactNode; 30 /** 31 * Loading indicator shown during in-flight fetches. 32 */ 33 loadingIndicator?: React.ReactNode; 34 /** 35 * Pre-resolved handle to display when available externally. 36 */ 37 handle?: string; 38 /** 39 * Preferred color scheme forwarded to renderer implementations. 40 */ 41 colorScheme?: 'light' | 'dark' | 'system'; 42} 43 44/** 45 * Props injected into custom profile renderer implementations. 46 */ 47export type BlueskyProfileRendererInjectedProps = { 48 /** 49 * Loaded profile record value. 50 */ 51 record: ProfileRecord; 52 /** 53 * Indicates whether the record is currently being fetched. 54 */ 55 loading: boolean; 56 /** 57 * Any error encountered while fetching the profile. 58 */ 59 error?: Error; 60 /** 61 * DID associated with the profile. 62 */ 63 did: string; 64 /** 65 * Human-readable handle for the DID, when known. 66 */ 67 handle?: string; 68 /** 69 * Blob URL for the user's avatar, when available. 70 */ 71 avatarUrl?: string; 72 /** 73 * Preferred color scheme for theming downstream components. 74 */ 75 colorScheme?: 'light' | 'dark' | 'system'; 76}; 77 78/** NSID for the canonical Bluesky profile collection. */ 79export const BLUESKY_PROFILE_COLLECTION = 'app.bsky.actor.profile'; 80 81/** 82 * Fetches and renders a Bluesky actor profile, optionally injecting custom presentation 83 * and providing avatar resolution support. 84 * 85 * @param did - DID whose profile record should be fetched. 86 * @param rkey - Record key within the profile collection (default `'self'`). 87 * @param renderer - Optional component override for custom rendering. 88 * @param fallback - Node rendered prior to loading state initialization. 89 * @param loadingIndicator - Node rendered while the profile request is in-flight. 90 * @param handle - Optional pre-resolved handle to display. 91 * @param colorScheme - Preferred color scheme forwarded to the renderer. 92 * @returns A rendered profile component with loading/error states handled. 93 */ 94export const BlueskyProfile: React.FC<BlueskyProfileProps> = ({ did: handleOrDid, rkey = 'self', renderer, fallback, loadingIndicator, handle, colorScheme }) => { 95 const Component: React.ComponentType<BlueskyProfileRendererInjectedProps> = renderer ?? ((props) => <BlueskyProfileRenderer {...props} />); 96 const { did, handle: resolvedHandle } = useDidResolution(handleOrDid); 97 const repoIdentifier = did ?? handleOrDid; 98 const effectiveHandle = handle ?? resolvedHandle ?? (handleOrDid.startsWith('did:') ? formatDidForLabel(repoIdentifier) : handleOrDid); 99 100 const Wrapped: React.FC<{ record: ProfileRecord; loading: boolean; error?: Error }> = (props) => { 101 const avatarCid = getAvatarCid(props.record); 102 const { url: avatarUrl } = useBlob(repoIdentifier, avatarCid); 103 return <Component {...props} did={repoIdentifier} handle={effectiveHandle} avatarUrl={avatarUrl} colorScheme={colorScheme} />; 104 }; 105 return ( 106 <AtProtoRecord<ProfileRecord> 107 did={repoIdentifier} 108 collection={BLUESKY_PROFILE_COLLECTION} 109 rkey={rkey} 110 renderer={Wrapped} 111 fallback={fallback} 112 loadingIndicator={loadingIndicator} 113 /> 114 ); 115}; 116 117export default BlueskyProfile;